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.21
Committed: Thu Mar 1 02:00:02 2001 UTC (23 years, 3 months ago) by ajm
Branch: MAIN
Changes since 1.20: +4 -2 lines
Log Message:
Now all configuration support is in place.  Full 1.1 support and configuration for it.
Still a few configuration bugs to iron out, but all the major construction and implementation is done.
Added debug messages to ConnectionHandler.
Fixed bug in the datapanel.

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.20 2001/02/27 23:31:32 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.20 $";
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 Conient.addMessage("Setting host list to:" + hostList);
503 String response = null;
504 _outBound.println("SETHOSTLIST");
505 _outBound.flush();
506 response = _inBound.readLine();
507 if (!response.equals("OK")) {
508 throw new IOException("server refused - data link possibly still open?");
509 }
510 _outBound.println(hostList);
511 _outBound.flush();
512 response = _inBound.readLine();
513 if (!response.equals("OK")) {
514 throw new IOException("server had trouble with our request");
515 }
516 success = true;
517 } catch (IOException e) {
518 Conient.addMessage("ERROR{control link}: when setting hostlist - " + e);
519 }
520 }
521 }
522 return success;
523 }
524
525
526 /**
527 * This method performs the STARTCONFIG command on
528 * the server. this method is called after a CONNECT,
529 * and when a GETCONFIGURATION command is called.
530 */
531 private void getConfigFromServer() {
532 if(_controlLink != null) {
533 try {
534 Conient.addMessage("Getting configuration from server");
535 String response = null;
536 _outBound.println("STARTCONFIG");
537 _outBound.flush();
538 response = _inBound.readLine();
539 if (!response.equals("OK")) {
540 throw new IOException("server refused (" + response + ")");
541 }
542 _configuration.readServerConfiguration(_inBound, _outBound);
543 _outBound.println("ENDCONFIG");
544 _outBound.flush();
545 response = _inBound.readLine();
546 if (!response.equals("OK")) {
547 throw new IOException("server reported error when finishing configuration");
548 }
549 } catch (IOException e) {
550 Conient.addMessage("ERROR{control link}: when getting configuration - " + e);
551 }
552 }
553 }
554
555 /**
556 * Handles opening pipes through firewalls.
557 * It checks the configuration for various entries
558 * to set up the link or not.
559 *
560 * If a link is setup it will return the name of the
561 * new server to connect to, if a link is NOT setup,
562 * it will simply return the original server name.
563 *
564 * The name of the machine that the pipe is setup on
565 * defaults to "localhost", unless the configuration
566 * specifies otherwise.
567 *
568 * Once it has run the firewall command it then waits
569 * a set period according to the config for the firewall
570 * pipe to be set up.
571 *
572 * The firewall process should be destroyed when the
573 * link is finished with.
574 *
575 * @param server the name of the server to open up the pipe to
576 * @param port the port number to open up the pipe to
577 * @param firewallProcess the holder for the new firewall process
578 * @return the server to connect to, as determined by this routine
579 */
580 private String handleFirewall(String server, int port, Process firewallProcess) {
581 String firewallCommand = _configuration.getProperty("firewall.command");
582 String useFirewall = _configuration.getProperty("useFirewall");
583 String firewallCommandWait = _configuration.getProperty("firewall.commandwait");
584 String firewallServer = _configuration.getProperty("firewall.server");
585 // if we are running firewall support...then lets start it
586 if (useFirewall.equals("1")) {
587 // clean up the command with what we want
588 firewallCommand = replaceText(firewallCommand, "%PORT%", new Integer(port).toString());
589 firewallCommand = replaceText(firewallCommand, "%SERVER%", server);
590 Conient.addMessage("WARNING{firewall}: firewall pipes requested, running pipe setup command \"" + firewallCommand + "\"");
591 try {
592 // run the command
593 firewallProcess = Runtime.getRuntime().exec(firewallCommand);
594
595 // work out how long we should wait before carrying on
596 int time = 0;
597 try {
598 time = Integer.parseInt(firewallCommandWait);
599 } catch (NumberFormatException e) {
600 time = 0;
601 }
602 if (time == 0) {
603 time = DEFAULT_FIREWALL_COMMANDWAIT;
604 }
605 Conient.addMessage("WARNING{firewall}: waiting " + time + " seconds for command to complete!");
606 // wait for the command to finish
607 try {
608 Thread.sleep(time * 1000);
609 } catch (InterruptedException e) {
610 }
611
612 // set the server we want to return
613 server = firewallServer;
614 if (server.equals("")) {
615 server = DEFAULT_FIREWALL_SERVER;
616 }
617 } catch (IOException e) {
618 Conient.addMessage("ERROR{firewall}: unable to start pipe to i-scream server");
619 }
620 }
621 return server;
622 }
623
624 /**
625 * Checks to see if the given firewall process
626 * has been created. If it has it calls destroy()
627 * on it.
628 *
629 * @param firewallProcess the process to check
630 */
631 private void closeFirewall(Process firewallProcess) {
632 if (firewallProcess != null) {
633 firewallProcess.destroy();
634 firewallProcess = null;
635 Conient.addMessage("WARNING{firewall}: firewall process destroyed");
636 }
637 }
638
639 /**
640 * Searches a string and replaces all occurences
641 * of the given search text with the given replacement
642 * text.
643 *
644 * @param text the text to search and replace in
645 * @param search the string to look for and replace
646 * @param replace the text to replace with
647 * @return the updated version of text
648 */
649 private String replaceText(String text, String search, String replace) {
650 StringBuffer textBuffer = new StringBuffer(text);
651 int currIndex = 0;
652 currIndex = text.indexOf(search, currIndex);
653 while(currIndex != -1) {
654 if (currIndex != -1) {
655 textBuffer.delete(currIndex, currIndex + search.length());
656 textBuffer.insert(currIndex, replace);
657 }
658 text = textBuffer.toString();
659 currIndex = text.indexOf(search, currIndex + search.length());
660 }
661 return new String(textBuffer);
662 }
663 //---ACCESSOR/MUTATOR METHODS---
664
665 //---ATTRIBUTES---
666
667 /**
668 * The state if this thread
669 */
670 private boolean _running = true;
671
672 /**
673 * A reference to the displaying class DataPanel
674 */
675 private DataPanel _data;
676
677 /**
678 * The queue that actions are added to by other parts of the system
679 */
680 private Queue _actionQueue;
681
682 /**
683 * The queue number that we are reading from on the action queue
684 */
685 private int _myQueue;
686
687 /**
688 * The control link socket
689 */
690 private Socket _controlLink;
691
692 /**
693 * The data link socket
694 */
695 private Socket _dataLink;
696
697 /**
698 * The input for the control link
699 */
700 private BufferedReader _inBound;
701
702 /**
703 * The output for the control link
704 */
705 private PrintWriter _outBound;
706
707 /**
708 * The input for the data link
709 */
710 private BufferedReader _dataInBound;
711
712 /**
713 * The output for the data link
714 */
715 private PrintWriter _dataOutBound;
716
717 /**
718 * A reference to the DataReader in use
719 */
720 private DataReader _dataReader;
721
722 /**
723 * A reference to the system configuration component
724 */
725 private Configuration _configuration = Configuration.getInstance();
726
727 /**
728 * The server we will be connecting to
729 */
730 private String _server = null;
731
732 /**
733 * The server that the config says we should connect to
734 */
735 private String _configuredServer = null;
736
737 /**
738 * The process used to start the firewall pipe
739 * for the control link.
740 */
741 private Process _controlFirewallProcess = null;
742
743 /**
744 * The process used to start the firewall pipe
745 * for the data link.
746 */
747 private Process _dataFirewallProcess = null;
748
749 //---STATIC ATTRIBUTES---
750
751 }