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.19
Committed: Tue Feb 27 03:09:58 2001 UTC (23 years, 3 months ago) by ajm
Branch: MAIN
Changes since 1.18: +7 -6 lines
Log Message:
Now has fully support for configuration modification, saving and loading.

Note there are still bugs, namely the server config is NOT treated seperately
from local config, as well as concurrency issues of loading in a config as
its changing.

Also not present is support for checking all REQUIRED configuration options
are present, so that will need to be done.

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.18 2001/02/26 00:25:00 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.18 $";
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").equals("0")) {
148 _actionQueue.add(new Integer(CONNECT));
149 }
150 if(!_configuration.getProperty("data.onstartconnect").equals("0")) {
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 } 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 getConfigFromServer();
454 break;
455 }
456 }
457 }
458
459 /**
460 * This method allows other classes
461 * to shutdown this connection handler.
462 */
463 public void shutdown() {
464 _running = false;
465 }
466
467 //---PRIVATE METHODS---
468
469 /**
470 * This method performs the STARTCONFIG command on
471 * the server. this method is called after a CONNECT,
472 * and when a GETCONFIGURATION command is called.
473 */
474 private void getConfigFromServer() {
475 if(_controlLink != null) {
476 try {
477 String response = null;
478 _outBound.println("STARTCONFIG");
479 _outBound.flush();
480 response = _inBound.readLine();
481 if (!response.equals("OK")) {
482 throw new IOException("server refused (" + response + ")");
483 }
484 _configuration.readServerConfiguration(_inBound, _outBound);
485 _outBound.println("ENDCONFIG");
486 _outBound.flush();
487 response = _inBound.readLine();
488 if (!response.equals("OK")) {
489 throw new IOException("server reported error when finishing configuration");
490 }
491 } catch (IOException e) {
492 Conient.addMessage("ERROR{control link}: when getting configuration - " + e);
493 }
494 }
495 }
496
497 /**
498 * Handles opening pipes through firewalls.
499 * It checks the configuration for various entries
500 * to set up the link or not.
501 *
502 * If a link is setup it will return the name of the
503 * new server to connect to, if a link is NOT setup,
504 * it will simply return the original server name.
505 *
506 * The name of the machine that the pipe is setup on
507 * defaults to "localhost", unless the configuration
508 * specifies otherwise.
509 *
510 * Once it has run the firewall command it then waits
511 * a set period according to the config for the firewall
512 * pipe to be set up.
513 *
514 * The firewall process should be destroyed when the
515 * link is finished with.
516 *
517 * @param server the name of the server to open up the pipe to
518 * @param port the port number to open up the pipe to
519 * @param firewallProcess the holder for the new firewall process
520 * @return the server to connect to, as determined by this routine
521 */
522 private String handleFirewall(String server, int port, Process firewallProcess) {
523 String firewallCommand = _configuration.getProperty("firewall.command");
524 String useFirewall = _configuration.getProperty("useFirewall");
525 String firewallCommandWait = _configuration.getProperty("firewall.commandwait");
526 String firewallServer = _configuration.getProperty("firewall.server");
527 // if we are running firewall support...then lets start it
528 if (!useFirewall.equals("0")) {
529 // clean up the command with what we want
530 firewallCommand = replaceText(firewallCommand, "%PORT%", new Integer(port).toString());
531 firewallCommand = replaceText(firewallCommand, "%SERVER%", server);
532 Conient.addMessage("WARNING{firewall}: firewall pipes requested, running pipe setup command \"" + firewallCommand + "\"");
533 try {
534 // run the command
535 firewallProcess = Runtime.getRuntime().exec(firewallCommand);
536
537 // work out how long we should wait before carrying on
538 int time = 0;
539 try {
540 time = Integer.parseInt(firewallCommandWait);
541 } catch (NumberFormatException e) {
542 time = 0;
543 }
544 if (time == 0) {
545 time = DEFAULT_FIREWALL_COMMANDWAIT;
546 }
547 Conient.addMessage("WARNING{firewall}: waiting " + time + " seconds for command to complete!");
548 // wait for the command to finish
549 try {
550 Thread.sleep(time * 1000);
551 } catch (InterruptedException e) {
552 }
553
554 // set the server we want to return
555 server = firewallServer;
556 if (server.equals("")) {
557 server = DEFAULT_FIREWALL_SERVER;
558 }
559 } catch (IOException e) {
560 Conient.addMessage("ERROR{firewall}: unable to start pipe to i-scream server");
561 }
562 }
563 return server;
564 }
565
566 /**
567 * Checks to see if the given firewall process
568 * has been created. If it has it calls destroy()
569 * on it.
570 *
571 * @param firewallProcess the process to check
572 */
573 private void closeFirewall(Process firewallProcess) {
574 if (firewallProcess != null) {
575 firewallProcess.destroy();
576 firewallProcess = null;
577 Conient.addMessage("WARNING{firewall}: firewall process destroyed");
578 }
579 }
580
581 /**
582 * Searches a string and replaces all occurences
583 * of the given search text with the given replacement
584 * text.
585 *
586 * @param text the text to search and replace in
587 * @param search the string to look for and replace
588 * @param replace the text to replace with
589 * @return the updated version of text
590 */
591 private String replaceText(String text, String search, String replace) {
592 StringBuffer textBuffer = new StringBuffer(text);
593 int currIndex = 0;
594 currIndex = text.indexOf(search, currIndex);
595 while(currIndex != -1) {
596 if (currIndex != -1) {
597 textBuffer.delete(currIndex, currIndex + search.length());
598 textBuffer.insert(currIndex, replace);
599 }
600 text = textBuffer.toString();
601 currIndex = text.indexOf(search, currIndex + search.length());
602 }
603 return new String(textBuffer);
604 }
605 //---ACCESSOR/MUTATOR METHODS---
606
607 //---ATTRIBUTES---
608
609 /**
610 * The state if this thread
611 */
612 private boolean _running = true;
613
614 /**
615 * A reference to the displaying class DataPanel
616 */
617 private DataPanel _data;
618
619 /**
620 * The queue that actions are added to by other parts of the system
621 */
622 private Queue _actionQueue;
623
624 /**
625 * The queue number that we are reading from on the action queue
626 */
627 private int _myQueue;
628
629 /**
630 * The control link socket
631 */
632 private Socket _controlLink;
633
634 /**
635 * The data link socket
636 */
637 private Socket _dataLink;
638
639 /**
640 * The input for the control link
641 */
642 private BufferedReader _inBound;
643
644 /**
645 * The output for the control link
646 */
647 private PrintWriter _outBound;
648
649 /**
650 * The input for the data link
651 */
652 private BufferedReader _dataInBound;
653
654 /**
655 * The output for the data link
656 */
657 private PrintWriter _dataOutBound;
658
659 /**
660 * A reference to the DataReader in use
661 */
662 private DataReader _dataReader;
663
664 /**
665 * A reference to the system configuration component
666 */
667 private Configuration _configuration = Configuration.getInstance();
668
669 /**
670 * The server we will be connecting to
671 */
672 private String _server = null;
673
674 /**
675 * The server that the config says we should connect to
676 */
677 private String _configuredServer = null;
678
679 /**
680 * The process used to start the firewall pipe
681 * for the control link.
682 */
683 private Process _controlFirewallProcess = null;
684
685 /**
686 * The process used to start the firewall pipe
687 * for the data link.
688 */
689 private Process _dataFirewallProcess = null;
690
691 //---STATIC ATTRIBUTES---
692
693 }