ViewVC Help
View File | Revision Log | Show Annotations | Revision Graph | Root Listing
root/i-scream/projects/cms/source/server/uk/org/iscream/cms/server/clientinterface/TCPControlHandler.java
Revision: 1.30
Committed: Sun Sep 25 09:57:41 2005 UTC (18 years, 7 months ago) by tdb
Branch: MAIN
CVS Tags: HEAD
Changes since 1.29: +4 -3 lines
Log Message:
Fix compile problems on j2se 1.5 - our Queue class conflicted with one in
java.util. Also fix an API issue when running the server on Windows - the
println method sends '\r\n' on Windows instead of '\n' on Unix, which
confuses applications such as ihost.

Patch provided by: skel

File Contents

# Content
1 /*
2 * i-scream central monitoring system
3 * http://www.i-scream.org
4 * Copyright (C) 2000-2002 i-scream
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 */
20
21 //---PACKAGE DECLARATION---
22 package uk.org.iscream.cms.server.clientinterface;
23
24 //---IMPORTS---
25 import uk.org.iscream.cms.util.*;
26 import uk.org.iscream.cms.server.componentmanager.*;
27 import uk.org.iscream.cms.server.core.*;
28 import java.net.Socket;
29 import java.net.ServerSocket;
30 import java.io.InputStream;
31 import java.io.OutputStream;
32 import java.io.IOException;
33 import java.io.BufferedReader;
34 import java.io.PrintWriter;
35 import java.io.InputStreamReader;
36
37
38 /**
39 * Acts as a Control Handler to a TCP based client.
40 *
41 * @author $Author: tdb $
42 * @version $Id: TCPControlHandler.java,v 1.29 2004/08/01 10:40:48 tdb Exp $
43 */
44 class TCPControlHandler extends Thread {
45
46 //---FINAL ATTRIBUTES---
47
48 /**
49 * The current CVS revision of this class
50 */
51 public final String REVISION = "$Revision: 1.29 $";
52
53 /**
54 * This is our protocol version. It is hardcoded to ensure
55 * that it doesn't get accidently changed. A change to the
56 * protocol would also require changing this classes code !
57 */
58 public static final String PROTOVER = "PROTOCOL 1.1";
59
60 //---STATIC METHODS---
61
62 //---CONSTRUCTORS---
63
64 /**
65 * Construct a new TCPControlHandler, and setup the reader
66 * and writer for the new Socket.
67 *
68 * @param socket The Socket connected to the new Client
69 * @param packetSorter A reference to the PacketSorter in the component
70 */
71 public TCPControlHandler(Socket socket, PacketSorter packetSorter) throws IOException {
72 // set the Thread name
73 setName("clientinterface.TCPControlHandler");
74
75 _socket = socket;
76 _packetSorter = packetSorter;
77 // setup the reader & writer
78 _socketIn = new BufferedReader(new InputStreamReader(_socket.getInputStream()));
79 _socketOut = new PrintWriter(_socket.getOutputStream(), true);
80 // set the default hostlist to "all host", for backward compatibility
81 _hostList = "";
82 _logger.write(toString(), Logger.SYSINIT, "created");
83 }
84
85 //---PUBLIC METHODS---
86
87 /**
88 * This method initiates the thread, setting things up, and then
89 * reading commands from the Client. It handles setting up of the
90 * DataHandler, and clean shutting down.
91 */
92 public void run() {
93 boolean run = true;
94 // Tell the client our protocol, and ask it for it's name
95 try {
96 send(PROTOVER);
97 _clientName = _socketIn.readLine();
98 if(_clientName==null) {
99 throw new IOException("Fatal error reading from client");
100 }
101 sendOK();
102 }
103 catch(IOException e) {
104 _logger.write(toString(), Logger.FATAL, "Fatal error, shutdown pending");
105 run=false;
106 }
107
108 _logger.write(toString(), Logger.DEBUG, "Client has connected: "+_clientName);
109
110 // loop until we decide to shutdown (run=false)
111 while(run) {
112 try {
113 String cmd = _socketIn.readLine();
114 if(cmd==null) {
115 throw new IOException("Fatal error reading from client");
116 }
117 // make a decision about what to do
118 if(cmd.equals("STARTCONFIG")) {
119 // get the configuration for this client
120 ConfigurationProxy cp = ConfigurationProxy.getInstance();
121 sendOK();
122 // get properties
123 cmd = _socketIn.readLine();
124 if(cmd==null) {
125 throw new IOException("Fatal error reading from client");
126 }
127 // provide all the requested properties
128 while(!cmd.equals("ENDCONFIG")) {
129 int midPoint = cmd.indexOf(";");
130 String config, property;
131 if(midPoint != -1) {
132 try {
133 config = cmd.substring(0, midPoint);
134 property = cmd.substring(midPoint+1, cmd.length());
135 if(!config.equals("") && !property.equals("")) {
136 // client is restricted to these properties and configs
137 if( (property.startsWith("Client.") || property.startsWith("Host."))
138 && (config.startsWith("Client.") || config.startsWith("Host.")) ) {
139 try {
140 String returnedProperty = cp.getProperty(config, property);
141 send(returnedProperty);
142 }
143 catch (PropertyNotFoundException e) {
144 sendERROR();
145 }
146 }
147 else {
148 sendERROR();
149 }
150 }
151 else {
152 sendERROR();
153 }
154 }
155 catch (IndexOutOfBoundsException e) {
156 sendERROR();
157 }
158 }
159 else {
160 sendERROR();
161 }
162 cmd = _socketIn.readLine();
163 if(cmd==null) {
164 throw new IOException("Fatal error reading from client");
165 }
166 }
167 sendOK();
168 _logger.write(toString(), Logger.DEBUG, "Client has been configured");
169
170 }
171 else if(cmd.equals("STARTDATA")) {
172 // if we don't have a DataHandler, set one up
173 if(_dataHandler == null) {
174 // This ACL isn't dynamic like the others, but is instead
175 // recreated each time a client connects. Maybe this ACL
176 // could instead be done to allow only the correct client
177 // to connect to the data channel?
178
179 // get our ACL from the configuration
180 ACL acl = null;
181 try {
182 String stringACL = ConfigurationProxy.getInstance().getProperty("ClientInterface", "ClientInterface.TCPDataChannelACL");
183 acl = new ACL(stringACL);
184 }
185 catch(PropertyNotFoundException e) {
186 _logger.write(toString(), Logger.WARNING, "No ACL found for ClientInterface (data channel listener): " + e);
187 }
188 // create a serversocket
189 // use an ACLServerSocket if we have an ACL
190 ServerSocket ss;
191 if(acl != null) {
192 ss = new ACLServerSocket(acl, 0);
193 }
194 else {
195 ss = new ServerSocket(0);
196 }
197 // get the port
198 String port = String.valueOf(ss.getLocalPort());
199 // tell the client the port
200 send(port);
201 // wait for the client to connect
202 Socket s = ss.accept();
203 // when we get the Socket back, give it to the data thread
204 TCPDataHandler dh = new TCPDataHandler(s);
205 // register the DataHandler's queue, giving the host list
206 _packetSorter.register(dh.getQueue(), _hostList);
207 try {
208 // startup a monitor on the DataHandler's queue
209 ConfigurationProxy cp = ConfigurationProxy.getInstance();
210 int queueMonitorInterval = Integer.parseInt(cp.getProperty("ClientInterface", "Queue.MonitorInterval"));
211 String queueName = _name + " TCPHandler:"+_socket.getInetAddress().getHostName();
212 dh.getQueue().startMonitor(queueMonitorInterval*1000, _packetSorter.getQueue(), queueName);
213 } catch (PropertyNotFoundException e) {
214 _logger.write(toString(), Logger.WARNING, "failed to find queue monitor config, disabling. " + e);
215 }
216 // start up the DataHandler
217 dh.start();
218 // Hold a reference to the DataHandler, so we can stop it later
219 _dataHandler = dh;
220 sendOK();
221 _logger.write(toString(), Logger.DEBUG, "Data stream started at Clients Request on port: "+port);
222 }
223 else {
224 sendERROR();
225 }
226 }
227 else if(cmd.equals("STOPDATA")) {
228 // attempt to close the data channel
229 if(closeData()) {
230 sendOK();
231 _logger.write(toString(), Logger.DEBUG, "Data stream stopped at Clients Request");
232 }
233 else {
234 sendERROR();
235 }
236 }
237 else if(cmd.equals("SETHOSTLIST")) {
238 // we can only set the host list when
239 // the DataHandler is not connected
240 if(_dataHandler == null) {
241 sendOK();
242 // read and set the host list
243 cmd = _socketIn.readLine();
244 if(cmd==null) {
245 throw new IOException("Fatal error reading from client");
246 }
247 _hostList = cmd;
248 sendOK();
249 }
250 else {
251 sendERROR();
252 }
253 }
254 else if(cmd.equals("DISCONNECT")) {
255 // we going to disconnect, so lets stop the main loop
256 run=false;
257 // if there is a DataHandler, we'd best shut it down
258 if(closeData()) {
259 _logger.write(toString(), Logger.DEBUG, "Data stream stopped at Clients Request");
260 }
261 // say bye to the client
262 sendOK();
263 // close the reader, writer and Socket
264 _socketIn.close();
265 _socketOut.close();
266 _socket.close();
267 _logger.write(toString(), Logger.DEBUG, "Closing at Clients Request");
268 }
269 else {
270 sendERROR();
271 }
272 }
273 catch(IOException e) {
274 // if we get an exception, the client has probably left, so we stop
275 run=false;
276 _logger.write(toString(), Logger.FATAL, "Fatal communication error, shutdown pending");
277 }
278 }
279 // we'll close any DataHandlers here that shouldn't be open still
280 if(closeData()) {
281 _logger.write(toString(), Logger.DEBUG, "Data stream stopped due to fatal client error");
282 }
283 _logger.write(toString(), Logger.DEBUG, "Shutting down Control Handler, client has gone.");
284 }
285
286 /**
287 * Overrides the {@link java.lang.Object#toString() Object.toString()}
288 * method to provide clean logging (every class should have this).
289 *
290 * This uses the uk.org.iscream.cms.util.NameFormat class
291 * to format the toString()
292 *
293 * @return the name of this class and its CVS revision
294 */
295 public String toString() {
296 return FormatName.getName(
297 _name,
298 getClass().getName(),
299 REVISION);
300 }
301
302 //---PRIVATE METHODS---
303
304 /**
305 * Attempt to close down the DataHandler. This will return
306 * true if it managed to close it down, or false if there
307 * wasn't a DataHandler to close.
308 *
309 * @return whether the channel could be closed
310 */
311 private boolean closeData() {
312 // if we have a DataHandler, shut it down
313 if(_dataHandler != null) {
314 // Deregister the DataHandler, giving the host list
315 _packetSorter.deregister(_dataHandler.getQueue(), _hostList);
316 // Shut down the data handler
317 _dataHandler.shutdown();
318 // Destroy our reference, leaving it for the GC
319 _dataHandler = null;
320 return true;
321 }
322 else {
323 return false;
324 }
325 }
326
327 /**
328 * Send an OK message to the outgoing Socket
329 */
330 private void sendOK() {
331 send("OK");
332 }
333
334 /**
335 * Send an ERROR message to the outgoing Socket.
336 */
337 private void sendERROR() {
338 send("ERROR");
339 }
340
341 /**
342 * Send any String message to the outgoing Socket.
343 *
344 * @param message The message/command to send.
345 */
346 private void send(String message) {
347 _socketOut.print(message + "\n");
348 _socketOut.flush();
349 }
350
351 //---ACCESSOR/MUTATOR METHODS---
352
353 //---ATTRIBUTES---
354
355 /**
356 * This is the friendly identifier of the
357 * component this class is running in.
358 * eg, a Filter may be called "filter1",
359 * If this class does not have an owning
360 * component, a name from the configuration
361 * can be placed here. This name could also
362 * be changed to null for utility classes.
363 */
364 private String _name = ClientInterfaceMain.NAME;
365
366 /**
367 * This holds a reference to the
368 * system logger that is being used.
369 */
370 private Logger _logger = ReferenceManager.getInstance().getLogger();
371
372 /**
373 * A reference to the Configuration Manager the system is using
374 */
375 private ConfigurationManager _configManager = ReferenceManager.getInstance().getCM();
376
377 /**
378 * The socket we are talking on
379 */
380 private Socket _socket;
381
382 /**
383 * A hook to the inbound data from the socket
384 */
385 private BufferedReader _socketIn;
386
387 /**
388 * A hook to the outbound stream for the socket
389 */
390 private PrintWriter _socketOut;
391
392 /**
393 * A reference to the PacketSorter.
394 */
395 private PacketSorter _packetSorter;
396
397 /**
398 * A reference to the DataHandler, if there is one
399 */
400 private TCPDataHandler _dataHandler;
401
402 /**
403 * The name of the Client we're connected to
404 */
405 private String _clientName;
406
407 /**
408 * The host list the Client has requested
409 */
410 private String _hostList;
411
412 //---STATIC ATTRIBUTES---
413
414 }