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.31
Committed: Fri Mar 23 04:05:26 2001 UTC (23 years, 2 months ago) by ajm
Branch: MAIN
CVS Tags: PROJECT_COMPLETION
Changes since 1.30: +6 -5 lines
Log Message:
Now only disconnects the data link on a data communication failure.

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.30 2001/03/23 03:58:26 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.30 $";
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 /**
115 * The default maximum queue size in use for the data queue
116 */
117 public static final int DEFAULT_QUEUE_LIMIT = 500;
118
119 /**
120 * The algorithm we use to remove items from the queue
121 * if it reaches its maximum size.
122 *
123 * We use a FIFO algorithm, see the uk.org.iscream.util.Queue
124 * class for other options.
125 */
126 public static final int QUEUE_ALGORITHM = Queue.FIRST;
127
128 //---STATIC METHODS---
129
130 //---CONSTRUCTORS---
131
132 /**
133 * Constructs new data handler.
134 * Needs a reference to the data panel so that
135 * it can set it processing inBound data
136 * Also gets a reference to the queue that will be
137 * used by other areas of the system to send it actions
138 *
139 * @param data the DataPanel in use
140 * @param actionQueue the actionQueue for this class
141 */
142 public ConnectionHandler(DataPanel data, Queue actionQueue) {
143 _data = data;
144 _actionQueue = actionQueue;
145 _myQueue = _actionQueue.getQueue();
146 _configuration.setConnectionHandler(this);
147 }
148
149 //---PUBLIC METHODS---
150
151
152 /**
153 * Starts this ConnectionHandler running.
154 * This basically runs until told to stop, it gets "actions"
155 * from its actionQueue and switch's on them do determine
156 * what it should do it then carries out the action
157 *
158 * For details on what each action does see the action
159 * types.
160 */
161 public void run() {
162 if(_configuration.getProperty("control.onstartconnect").equals("1")) {
163 _actionQueue.add(new Integer(CONNECT));
164 }
165 if(_configuration.getProperty("data.onstartconnect").equals("1")) {
166 _actionQueue.add(new Integer(STARTDATA));
167 }
168 while(_running) {
169 // we wait for a call...
170 int action = 0;
171 try {
172 action = ((Integer) _actionQueue.get(_myQueue)).intValue();
173 } catch (InvalidQueueException e) {
174 // we 're never going to get this
175 // but if we do we should do something nasty
176 throw new RuntimeException("unable to retrieve events from actionQueue!");
177 }
178
179 // examine our action...
180 // if it was to connect...then we connect...
181 switch(action) {
182 case CONNECT:
183 if (_controlLink == null) {
184 try {
185 // get the server name from the config
186 _configuredServer = _configuration.getProperty("control.server");
187
188 if (_configuredServer == null) {
189 throw new IOException("no i-scream server in current configuration");
190 }
191
192 // open the socket to the server and bind the IO
193 // get the port from the config
194 String portString = _configuration.getProperty("control.port");
195 if (portString == null) {
196 throw new IOException("no i-scream server port in current configuration");
197 }
198 int port = 0;
199 try {
200 port = Integer.parseInt(portString);
201 } catch (NumberFormatException e) {
202 throw new IOException("no valid i-scream server port in current configuration");
203 }
204
205 // start firewall if needed
206 _server = handleFirewall(_configuredServer, port, _controlFirewallProcess);
207
208 Conient.setControlStatus("Connecting to - " + _server);
209 _controlLink = new Socket(_server, port);
210 _inBound = new BufferedReader(new InputStreamReader(_controlLink.getInputStream()));
211 _outBound = new PrintWriter(_controlLink.getOutputStream());
212 Conient.setControlStatus("Connection Established - " + _server);
213
214 String response;
215 response = _inBound.readLine();
216
217 // check the servers Protocol Identity against our own
218 // we SHOULD be backwards compatible, so we can continue if
219 // they are using a newer protocol, anything else then we die
220 double serverProtocolVersion = Double.parseDouble(response.substring(9, response.length()));
221 Conient.addMessage("Protocol Versions: server [" + serverProtocolVersion + "] client [" + PROTOCOL_VERSION + "]");
222 if (serverProtocolVersion > PROTOCOL_VERSION) {
223 Conient.addMessage("WARNING{control link}: server is using a newer protocol (" + response + "), please update your client, continuing with old protocol (PROTOCOL " + PROTOCOL_VERSION + ")" );
224 } else if (serverProtocolVersion < PROTOCOL_VERSION) {
225 // tidy up
226 throw new IOException("incompatible protocol version");
227 }
228
229 // send the name of the client
230 _outBound.println(_configuration.getProperty("clientname"));
231 _outBound.flush();
232 response = _inBound.readLine();
233 if (!response.equals("OK")) {
234 // tidy up
235
236 throw new IOException("client name rejected - " + _configuration.getProperty("clientname"));
237 }
238
239 } catch (IOException e) {
240 // print the error and tidy up what's left
241 Conient.addMessage("ERROR{control link}: " + e);
242 _controlLink = null;
243 // and the firewall handler if there is one
244 closeFirewall(_controlFirewallProcess);
245 _actionQueue.clearQueue(_myQueue);
246 Conient.setControlStatus("Disconnected");
247 }
248 } else {
249 Conient.addMessage("WARNING{control link}: already established");
250 }
251 break;
252 case STARTDATA:
253 // as long as the data link hasn't been established
254 // we want to establish it
255 if (_dataLink == null) {
256 // check that the control link is open, if it isn't we
257 // might want to sort that problemo out
258 // we do this by simply queueing the event to occour, then
259 // this event to run again ;-)
260 if(_controlLink == null) {
261 Conient.addMessage("WARNING{data link}: control link not established - queueing start events");
262 _actionQueue.add(new Integer(CONNECT));
263 _actionQueue.add(new Integer(STARTDATA));
264 } else {
265 try {
266 // set our host list if we know we have one we need to set
267 String hostList = _configuration.getProperty("hostList");
268 boolean hostListSet = false;
269 // send our hostList if we have
270 if (_configuration.getProperty("useHostList").equals("1")) {
271 if (hostList.equals("")) {
272 Conient.addMessage("WARNING{control link}: your host list is empty, the server will send ALL hosts");
273 }
274 hostListSet = setHostList(hostList);
275 // if not, indicate we want the lot
276 } else {
277 hostListSet = setHostList("");
278 }
279 // warn if there was a problem, it will have already error'd
280 if (!hostListSet) {
281 Conient.addMessage("WARNING{control link}: unable to set host list");
282 }
283 // ask the server to start the data link
284 String response;
285 _outBound.println("STARTDATA");
286 _outBound.flush();
287 response = _inBound.readLine();
288
289 // see if the server suggested a good port
290 if (response.equals("ERROR")) {
291 throw new IOException("server unable to start data link at this time");
292 }
293 int port = 0;
294 try {
295 port = Integer.parseInt(response);
296 } catch (NumberFormatException e) {
297 throw new IOException("invalid data port suggested by server - " + response);
298 }
299
300 // start firewall if needed
301 _server = handleFirewall(_configuredServer, port, _dataFirewallProcess);
302 Conient.setDataStatus("Connecting to - " + _server + ":" + response);
303
304 _dataLink = new Socket(_server, port);
305
306 response = _inBound.readLine();
307 if (!response.equals("OK")) {
308 throw new IOException("server reported error establishing data channel");
309 }
310
311 // if the socket was ok, then we attack our IO hooks
312 _dataInBound = new BufferedReader(new InputStreamReader(_dataLink.getInputStream()));
313 _dataOutBound = new PrintWriter(_dataLink.getOutputStream());
314 Conient.setDataStatus("Connection Established - " + _server);
315 // now we want to start reading the data in
316 // so we start the appropriate components on their way
317 // we create a queue to give to both the reader and the
318 // displayer
319
320 String configQueueSize = _configuration.getProperty("dataQueueSize");
321 int queueLimit = DEFAULT_QUEUE_LIMIT;
322 if (configQueueSize != null) {
323 try {
324 queueLimit = Integer.parseInt(configQueueSize);
325 } catch (NumberFormatException e) {
326 // if it fails it will be default
327 Conient.addMessage("WARNING{data link}: invalid queue size in configuration, using default of - " + queueLimit);
328 }
329 }
330
331 Queue theQueue = new Queue(queueLimit, QUEUE_ALGORITHM);
332 _dataReader = new DataReader(_dataInBound, theQueue, this);
333 _data.setQueue(theQueue);
334 _data.cleanUpTabs();
335
336 // start the data rocking
337 new Thread(_data).start();
338 _dataReader.start();
339 // finished for us....
340 } catch (IOException e) {
341 // print the error and tidy up what's left
342 Conient.addMessage("ERROR{data link}: " + e);
343 _dataLink = null;
344 // and the firewall handler if there is one
345 closeFirewall(_dataFirewallProcess);
346 _actionQueue.clearQueue(_myQueue);
347 Conient.setDataStatus("Disconnected");
348 }
349 }
350 } else {
351 Conient.addMessage("WARNING{data link}: already established");
352 }
353 break;
354 case STOPDATA:
355 if(_dataLink != null) {
356 try {
357 String response;
358 // shut down the data link
359 Conient.setDataStatus("Disconnecting - " + _server);
360
361 // close the reader
362 _dataReader.shutdown();
363 // wait for it to close
364 boolean dirtyShutdown = true;
365 long startTime = System.currentTimeMillis();
366 while((System.currentTimeMillis() - startTime) < (DATAREADER_SHUTDOWN_TIMEOUT * 1000)) {
367 if (!_dataReader.isAlive()) {
368 dirtyShutdown = false;
369 break;
370 }
371 }
372 // warn if it didn't shutdown in time
373 if (dirtyShutdown) {
374 Conient.addMessage("WARNING{data link}: data reader thread did not close within timeout, killing its IO anyway!");
375 }
376
377 // tell the server
378 _outBound.println("STOPDATA");
379 _outBound.flush();
380 response = _inBound.readLine();
381
382 // check the server was ok with our request...
383 // even if it wasn't we will go anyway!
384 if (!response.equals("OK")) {
385 throw new IOException("server didn't OK request to stop data channel - stopping anyway");
386 }
387
388
389 // close the lot down
390 _dataInBound.close();
391 _dataOutBound.close();
392 _dataLink.close();
393 // get rid of the socket
394 _dataLink = null;
395
396 // and the firewall handler if there is one
397 closeFirewall(_dataFirewallProcess);
398
399 Conient.setDataStatus("Disconnected");
400 } catch (IOException e) {
401 // print the error and tidy up what's left
402 Conient.addMessage("ERROR{data link}: " + e);
403 try {
404 _dataOutBound.close();
405 _dataInBound.close();
406 _dataLink.close();
407 // and the firewall handler if there is one
408 closeFirewall(_dataFirewallProcess);
409 } catch (IOException e2) {
410 Conient.addMessage("CRITICAL{control link}: unable to close socket - " + e2);
411 }
412 _dataLink = null;
413 _actionQueue.clearQueue(_myQueue);
414 Conient.setDataStatus("Disconnected");
415 }
416 } else {
417 Conient.addMessage("WARNING{data link}: already disconnected");
418 }
419 break;
420 case DISCONNECT:
421 if (_controlLink != null) {
422 if (_dataLink != null) {
423 // we want to tell ourselves to stop it
424 Conient.addMessage("WARNING{control link}: data link not disconnected - queueing stop events");
425 _actionQueue.add(new Integer(STOPDATA));
426 _actionQueue.add(new Integer(DISCONNECT));
427 } else {
428 try {
429 // request the server to disconnect
430 String response;
431 _outBound.println("DISCONNECT");
432 _outBound.flush();
433 response = _inBound.readLine();
434
435 // check the server was ok with our request...
436 // even if it wasn't we will go anyway!
437 if (!response.equals("OK")) {
438 throw new IOException("server didn't OK request to stop control channel - stopping anyway");
439 }
440
441 // then lets shutdown the link
442 Conient.setControlStatus("Disconnecting - " + _server);
443 _inBound.close();
444 _outBound.close();
445 _controlLink.close();
446 // for good measure
447 _controlLink = null;
448
449 // and the firewall handler if there is one
450 closeFirewall(_controlFirewallProcess);
451
452 Conient.setControlStatus("Disconnected");
453 } catch (IOException e) {
454 Conient.addMessage("ERROR{control link}: " + e);
455 try {
456 _inBound.close();
457 _outBound.close();
458 _controlLink.close();
459 // and the firewall handler if there is one
460 closeFirewall(_controlFirewallProcess);
461 } catch (IOException e2) {
462 Conient.addMessage("CRITICAL{control link}: unable to close socket - " + e2);
463 }
464 _controlLink = null;
465 _actionQueue.clearQueue(_myQueue);
466 Conient.setControlStatus("Disconnected");
467 }
468 }
469 } else {
470 Conient.addMessage("WARNING{control link}: already disconnected");
471 }
472 break;
473 case QUIT:
474 Conient.addMessage("Exiting.");
475 try {
476 // stop data and control if data up
477 if (_dataLink != null) {
478 _actionQueue.add(new Integer(STOPDATA));
479 _actionQueue.add(new Integer(DISCONNECT));
480 _actionQueue.add(new Integer(QUIT));
481 throw new IOException();
482 }
483 // stop control
484 if (_controlLink != null) {
485 _actionQueue.add(new Integer(DISCONNECT));
486 _actionQueue.add(new Integer(QUIT));
487 throw new IOException();
488 }
489 Conient.addMessage("Finished.");
490 // go!
491 System.exit(0);
492 } catch (IOException e) {
493 Conient.addMessage("WARNING: open connections detected - queueing stop events");
494 }
495 break;
496 }
497 }
498 }
499
500 /**
501 * This method is called by the Configuration object
502 * when a request is made for a server property.
503 * The Configuration object is informed of the Connection
504 * Handler's whereabouts on construction of the ConnectionHandler.
505 *
506 * This implements the STARTCONFIG command in the 1.0 protocol.
507 */
508 public String getConfigFromServer(String configName, String propertyName) {
509 String property = "";
510 if(_controlLink != null) {
511 try {
512 Conient.addMessage("Getting configuration from server");
513 String response = null;
514 _outBound.println("STARTCONFIG");
515 _outBound.flush();
516 response = _inBound.readLine();
517 if (!response.equals("OK")) {
518 throw new IOException("server refused (" + response + ")");
519 }
520
521 _outBound.println(configName + ";" + propertyName);
522 _outBound.flush();
523 response = _inBound.readLine();
524 if (response.equals("ERROR")) {
525 throw new IOException("server did not returned ERROR");
526 }
527 property = response;
528 _outBound.println("ENDCONFIG");
529 _outBound.flush();
530 response = _inBound.readLine();
531 if (!response.equals("OK")) {
532 throw new IOException("server reported error when finishing configuration");
533 }
534 } catch (IOException e) {
535 Conient.addMessage("ERROR{control link}: when getting configuration - " + e);
536 }
537 }
538 return property;
539 }
540
541 /**
542 * This method allows other classes
543 * to shutdown this connection handler.
544 */
545 public void shutdown() {
546 _running = false;
547 }
548
549 /**
550 * Called by the DataReader if the link
551 * is unexpectedly lost. This closes the
552 * data link.
553 */
554 public void shutdownDataLink() {
555 _actionQueue.add(new Integer(STOPDATA));
556 }
557
558 //---PRIVATE METHODS---
559
560 /**
561 * This method performs the SETHOSTLIST command.
562 * This is run by the CONNECT command.
563 * It tells the server which hosts we are interested
564 * in, if "" is sent, this indicates we want ALL hosts.
565 *
566 * @param hostList the list of hosts as gained from the config
567 * @return if we succeeded in setting the host list
568 */
569 private boolean setHostList(String hostList) {
570 boolean success = false;
571 // must have a control link open
572 if (_controlLink != null) {
573 // data link must be closed (according to 1.1 PROTOCOL)
574 if(_dataLink == null) {
575 try {
576 Conient.addMessage("Setting host list to:" + hostList);
577 String response = null;
578 _outBound.println("SETHOSTLIST");
579 _outBound.flush();
580 response = _inBound.readLine();
581 if (!response.equals("OK")) {
582 throw new IOException("server refused - data link possibly still open?");
583 }
584 _outBound.println(hostList);
585 _outBound.flush();
586 response = _inBound.readLine();
587 if (!response.equals("OK")) {
588 throw new IOException("server had trouble with our request");
589 }
590 success = true;
591 } catch (IOException e) {
592 Conient.addMessage("ERROR{control link}: when setting hostlist - " + e);
593 }
594 }
595 }
596 return success;
597 }
598
599 /**
600 * Handles opening pipes through firewalls.
601 * It checks the configuration for various entries
602 * to set up the link or not.
603 *
604 * If a link is setup it will return the name of the
605 * new server to connect to, if a link is NOT setup,
606 * it will simply return the original server name.
607 *
608 * The name of the machine that the pipe is setup on
609 * defaults to "localhost", unless the configuration
610 * specifies otherwise.
611 *
612 * Once it has run the firewall command it then waits
613 * a set period according to the config for the firewall
614 * pipe to be set up.
615 *
616 * The firewall process should be destroyed when the
617 * link is finished with.
618 *
619 * @param server the name of the server to open up the pipe to
620 * @param port the port number to open up the pipe to
621 * @param firewallProcess the holder for the new firewall process
622 * @return the server to connect to, as determined by this routine
623 */
624 private String handleFirewall(String server, int port, Process firewallProcess) {
625 String firewallCommand = _configuration.getProperty("firewall.command");
626 String useFirewall = _configuration.getProperty("useFirewall");
627 String firewallCommandWait = _configuration.getProperty("firewall.commandwait");
628 String firewallServer = _configuration.getProperty("firewall.server");
629 // if we are running firewall support...then lets start it
630 if (useFirewall.equals("1")) {
631 // clean up the command with what we want
632 firewallCommand = StringUtils.replaceText(firewallCommand, "%PORT%", new Integer(port).toString());
633 firewallCommand = StringUtils.replaceText(firewallCommand, "%SERVER%", server);
634 Conient.addMessage("WARNING{firewall}: firewall pipes requested, running pipe setup command \"" + firewallCommand + "\"");
635 try {
636 // run the command
637 firewallProcess = Runtime.getRuntime().exec(firewallCommand);
638
639 // work out how long we should wait before carrying on
640 int time = 0;
641 try {
642 time = Integer.parseInt(firewallCommandWait);
643 } catch (NumberFormatException e) {
644 time = 0;
645 }
646 if (time == 0) {
647 time = DEFAULT_FIREWALL_COMMANDWAIT;
648 }
649 Conient.addMessage("WARNING{firewall}: waiting " + time + " seconds for command to complete!");
650 // wait for the command to finish
651 try {
652 Thread.sleep(time * 1000);
653 } catch (InterruptedException e) {
654 }
655
656 // set the server we want to return
657 server = firewallServer;
658 if (server.equals("")) {
659 server = DEFAULT_FIREWALL_SERVER;
660 }
661 } catch (IOException e) {
662 Conient.addMessage("ERROR{firewall}: unable to start pipe to i-scream server");
663 }
664 }
665 return server;
666 }
667
668 /**
669 * Checks to see if the given firewall process
670 * has been created. If it has it calls destroy()
671 * on it.
672 *
673 * @param firewallProcess the process to check
674 */
675 private void closeFirewall(Process firewallProcess) {
676 if (firewallProcess != null) {
677 firewallProcess.destroy();
678 firewallProcess = null;
679 Conient.addMessage("WARNING{firewall}: firewall process destroyed");
680 }
681 }
682
683 //---ACCESSOR/MUTATOR METHODS---
684
685 //---ATTRIBUTES---
686
687 /**
688 * The state if this thread
689 */
690 private boolean _running = true;
691
692 /**
693 * A reference to the displaying class DataPanel
694 */
695 private DataPanel _data;
696
697 /**
698 * The queue that actions are added to by other parts of the system
699 */
700 private Queue _actionQueue;
701
702 /**
703 * The queue number that we are reading from on the action queue
704 */
705 private int _myQueue;
706
707 /**
708 * The control link socket
709 */
710 private Socket _controlLink;
711
712 /**
713 * The data link socket
714 */
715 private Socket _dataLink;
716
717 /**
718 * The input for the control link
719 */
720 private BufferedReader _inBound;
721
722 /**
723 * The output for the control link
724 */
725 private PrintWriter _outBound;
726
727 /**
728 * The input for the data link
729 */
730 private BufferedReader _dataInBound;
731
732 /**
733 * The output for the data link
734 */
735 private PrintWriter _dataOutBound;
736
737 /**
738 * A reference to the DataReader in use
739 */
740 private DataReader _dataReader;
741
742 /**
743 * A reference to the system configuration component
744 */
745 private Configuration _configuration = Configuration.getInstance();
746
747 /**
748 * The server we will be connecting to
749 */
750 private String _server = null;
751
752 /**
753 * The server that the config says we should connect to
754 */
755 private String _configuredServer = null;
756
757 /**
758 * The process used to start the firewall pipe
759 * for the control link.
760 */
761 private Process _controlFirewallProcess = null;
762
763 /**
764 * The process used to start the firewall pipe
765 * for the data link.
766 */
767 private Process _dataFirewallProcess = null;
768
769 //---STATIC ATTRIBUTES---
770
771 }