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.16
Committed: Sun Feb 4 23:46:08 2001 UTC (23 years, 4 months ago) by ajm
Branch: MAIN
Changes since 1.15: +24 -27 lines
Log Message:
now has a firewallClose handler

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