View Javadoc

1   /*
2   Copyright (C) 2007 Dirk Huenniger
3   
4   This library is free software; you can redistribute it and/or
5   modify it under the terms of the GNU Lesser General Public
6   License as published by the Free Software Foundation; either
7   version 2.1 of the License, or (at your option) any later version.
8   
9   This library is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  Lesser General Public License for more details.
13  
14  You should have received a copy of the GNU Lesser General Public
15  License along with this library; if not, write to the Free Software
16  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
17   */
18  package org.indi.server;
19  
20  import java.io.IOException;
21  import java.io.PipedInputStream;
22  import java.io.PipedOutputStream;
23  import java.nio.ByteBuffer;
24  import java.nio.channels.ReadableByteChannel;
25  import java.nio.channels.SelectableChannel;
26  import java.nio.channels.SelectionKey;
27  import java.util.HashMap;
28  import java.util.Map;
29  import java.util.Queue;
30  
31  import javax.xml.parsers.ParserConfigurationException;
32  import javax.xml.parsers.SAXParser;
33  import javax.xml.parsers.SAXParserFactory;
34  
35  import org.indi.clientmessages.BlobEnable;
36  import org.indi.clientmessages.EnableBlob;
37  import org.indi.clientmessages.GetProperties;
38  import org.indi.objects.BlobVector;
39  import org.indi.objects.Message;
40  import org.indi.objects.TransferType;
41  import org.indi.objects.Vector;
42  import org.indi.reactor.OutputQueue;
43  import org.indi.reactor.Reactor;
44  import org.xml.sax.SAXException;
45  
46  /**
47   * State class to keep track wheather blobs a enabled for a particular device
48   * and/or its subsystems
49   * 
50   * @author Dirk Hünniger
51   * 
52   */
53  class BlobEnabler {
54      /**
55       * the default BlobEnable state for the whole device
56       */
57      public BlobEnable defaults;
58      /**
59       * the BlobEnable state of each property of the device
60       */
61      public Map<String, BlobEnable> specific;
62  
63      /**
64       * class constructor
65       * 
66       */
67      public BlobEnabler() {
68  	this.specific = new HashMap<String, BlobEnable>();
69  	this.defaults = BlobEnable.Never;
70      }
71  }
72  
73  /**
74   * A class to handle a connection to a single client
75   * 
76   * @author Dirk Hünniger
77   * 
78   */
79  public class ClientHandler extends OutputQueue implements Runnable {
80      /**
81       * writeable end of the stream from the client to the parsing thread
82       */
83      private final PipedInputStream fromClient;
84      /**
85       * readable end of the stream from the client to the parsing thread
86       */
87      private final PipedOutputStream toThread;
88      /**
89       * Queue from the parsing thread to the device drivers
90       */
91      private Queue<java.lang.Object> threadToDriverQueue;
92      /**
93       * The parsed used to parse the xml received from the client
94       */
95      private final SAXParser parser;
96      /**
97       * the blob enable state of each device.
98       */
99      private Map<String, BlobEnabler> blobEnableMap = null;
100     /**
101      * the name of the device this client is interested in. any if null
102      */
103     private String device = null;
104     /**
105      * true if the client is interested in all devices
106      */
107     private boolean allDevices = false;
108     /**
109      * the default blobenable state for all devices
110      */
111     BlobEnable defaults = BlobEnable.Never;
112 
113     /**
114      * class constructor
115      * 
116      * @param r
117      *                the reactor to register with
118      * @param ch
119      *                the channel to communicate with the client
120      * @throws IOException
121      * @throws ParserConfigurationException
122      * @throws SAXException
123      */
124     public ClientHandler(Reactor r, SelectableChannel ch) throws IOException,
125 	    ParserConfigurationException, SAXException {
126 	super(r, ch);
127 	this.fromClient = new PipedInputStream();
128 	this.toThread = new PipedOutputStream(this.fromClient);
129 	this.toThread.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?> <doc>"
130 		.getBytes());
131 	this.parser = SAXParserFactory.newInstance().newSAXParser();
132 	this.blobEnableMap = new HashMap<String, BlobEnabler>();
133     }
134 
135     /**
136      * set the queue to send parsed messages from the parsing thread to the
137      * device drivers
138      * 
139      * @param queue
140      */
141     public void setQueue(Queue<java.lang.Object> queue) {
142 	this.threadToDriverQueue = queue;
143 	(new Thread(this)).start();
144     }
145 
146     /**
147      * start method parsing thread (run asynchornously)
148      */
149     public void run() {
150 	SaxHandler h = new SaxHandler(this.threadToDriverQueue, this);
151 	try {
152 	    this.parser.parse(this.fromClient, h);
153 	} catch (SAXException e) {
154 	    e.printStackTrace();
155 	} catch (IOException e) {
156 	    e.printStackTrace();
157 	}
158     }
159 
160     @Override
161     /**
162      * called by the reactor when new data is ready for reading from the
163      * client
164      */
165     public void onRead() throws IOException {
166 	ByteBuffer input = ByteBuffer.allocate(10000);
167 	try {
168 	    ((ReadableByteChannel) this.channel).read(input);
169 	} catch (IOException e) {
170 	    register(this.registeredOperations
171 		    - (SelectionKey.OP_READ & this.registeredOperations));
172 	    return;
173 	}
174 	input.flip();
175 	if (input.position() == input.limit()) {
176 	    onClientDisconnected();
177 	    return;
178 	}
179 	if (input.position() == input.limit()) {
180 	    register(this.registeredOperations
181 		    - (SelectionKey.OP_READ & this.registeredOperations));
182 	}
183 	this.toThread.write(input.array(), input.position(), input.limit());
184 	input.clear();
185     }
186 
187     /**
188      * called when the client disconnects
189      * 
190      */
191     private void onClientDisconnected() {
192 	this.reactor.unregister(this);
193     }
194 
195     /**
196      * called when a get properties message is received from the client
197      * 
198      * @param o
199      */
200     public void onGetProperties(GetProperties o) {
201 	if (o.device == null) {
202 	    this.allDevices = true;
203 	} else {
204 	    this.device = o.device;
205 	}
206     }
207 
208     /**
209      * called when an enable blob message ist received from the client
210      * 
211      * @param eb
212      *                the blobenable object describing the request of the
213      *                client
214      */
215     public synchronized void onEnableBlob(EnableBlob eb) {
216 	if (this.device == null) {
217 	    this.defaults = eb.blobenable;
218 	}
219 	BlobEnabler ber = this.blobEnableMap.get(eb.device);
220 	if (ber == null) {
221 	    BlobEnabler be = new BlobEnabler();
222 	    if (eb.name == null) {
223 		be.defaults = eb.blobenable;
224 		this.blobEnableMap.put(eb.device, be);
225 	    } else {
226 		be.specific.put(eb.name, eb.blobenable);
227 		this.blobEnableMap.put(eb.device, be);
228 	    }
229 	} else {
230 	    if (eb.name == null) {
231 		ber.defaults = eb.blobenable;
232 	    } else {
233 		ber.specific.put(eb.name, eb.blobenable);
234 	    }
235 	}
236     }
237 
238     /**
239      * Checkes whenther property of a given kind should sent to client using
240      * a specific blobenable state.
241      * 
242      * @param blob
243      *                ture if the property in question is a BLOB
244      * @param be
245      *                the blobenable state to used for the check
246      * @return true is the property should be sent to the client
247      */
248     private static boolean getEnabled(boolean blob, BlobEnable be) {
249 	if (blob) {
250 	    switch (be) {
251 	    case Only:
252 		return true;
253 	    case Also:
254 		return true;
255 	    case Never:
256 		return false;
257 	    }
258 	} else {
259 	    switch (be) {
260 	    case Only:
261 		return false;
262 	    case Also:
263 		return true;
264 	    case Never:
265 		return true;
266 	    }
267 	}
268 	throw new RuntimeException();
269     }
270 
271     /**
272      * Checks whether a given indivector should be send to the client with
273      * respect to the blobenable configuration for the client.
274      * 
275      * @param vec
276      *                the indivector to be checked
277      * @return true if the given indivector should be send to the client
278      */
279     public synchronized boolean getVectorEnabled(Vector vec) {
280 	BlobEnabler ber = this.blobEnableMap.get(vec.getDevice());
281 	if (ber == null) {
282 	    return getEnabled(vec instanceof BlobVector, this.defaults);
283 	} else {
284 	    BlobEnable be = ber.specific.get(vec.getName());
285 	    if (be == null) {
286 		return getEnabled(vec instanceof BlobVector, ber.defaults);
287 	    } else {
288 		return getEnabled(vec instanceof BlobVector, be);
289 	    }
290 	}
291     }
292 
293     /**
294      * send a particular indiobject to the client
295      * 
296      * @param object
297      *                the object to be send
298      * @param type
299      *                the way the obejct should be send
300      * @param message
301      *                the message to be sent along wiht the object
302      */
303     public synchronized void send(org.indi.objects.Object object,
304 	    TransferType type, String message) {
305 	String dev = null;
306 	if (object instanceof Vector) {
307 	    Vector vec = (Vector) object;
308 	    dev = vec.getDevice();
309 	    if (!getVectorEnabled(vec)) {
310 		return;
311 	    }
312 	}
313 	if (object instanceof Message) {
314 	    dev = ((Message) object).getDevice();
315 	    BlobEnabler ber = this.blobEnableMap.get(dev);
316 	    if (ber == null) {
317 		if (!getEnabled(false, this.defaults)) {
318 		    return;
319 		}
320 	    } else {
321 		if (!getEnabled(false, ber.defaults)) {
322 		    return;
323 		}
324 	    }
325 	}
326 	if ((dev == this.device) | (this.allDevices)) {
327 	    String data = object.getXML(type, message);
328 	    ByteBuffer output = ByteBuffer.allocate(data.length());
329 	    output.put(data.getBytes());
330 	    output.flip();
331 	    write(output);
332 	}
333     }
334 }