001: /*
002: *
003: * Copyright (c) 2007, Sun Microsystems, Inc.
004: *
005: * All rights reserved.
006: *
007: * Redistribution and use in source and binary forms, with or without
008: * modification, are permitted provided that the following conditions
009: * are met:
010: *
011: * * Redistributions of source code must retain the above copyright
012: * notice, this list of conditions and the following disclaimer.
013: * * Redistributions in binary form must reproduce the above copyright
014: * notice, this list of conditions and the following disclaimer in the
015: * documentation and/or other materials provided with the distribution.
016: * * Neither the name of Sun Microsystems nor the names of its contributors
017: * may be used to endorse or promote products derived from this software
018: * without specific prior written permission.
019: *
020: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
021: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
022: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
023: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
024: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
025: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
026: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
027: * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
028: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
029: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
030: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
031: */
032: package example.bluetooth.demo;
033:
034: import java.io.ByteArrayOutputStream;
035: import java.io.IOException;
036: import java.io.InputStream;
037: import java.io.OutputStream;
038:
039: import java.util.Hashtable;
040: import java.util.Vector;
041:
042: // jsr082 API
043: import javax.bluetooth.DataElement;
044: import javax.bluetooth.DiscoveryAgent;
045: import javax.bluetooth.LocalDevice;
046: import javax.bluetooth.ServiceRecord;
047: import javax.bluetooth.ServiceRegistrationException;
048: import javax.bluetooth.UUID;
049:
050: // midp/cldc API
051: import javax.microedition.io.Connector;
052: import javax.microedition.io.StreamConnection;
053: import javax.microedition.io.StreamConnectionNotifier;
054:
055: /**
056: * Established the BT service, accepts connections
057: * and send the requested image silently.
058: *
059: * @version ,
060: */
061: final class BTImageServer implements Runnable {
062: /** Describes this server */
063: private static final UUID PICTURES_SERVER_UUID = new UUID(
064: "F0E0D0C0B0A000908070605040302010", false);
065:
066: /** The attribute id of the record item with images names. */
067: private static final int IMAGES_NAMES_ATTRIBUTE_ID = 0x4321;
068:
069: /** Keeps the local device reference. */
070: private LocalDevice localDevice;
071:
072: /** Accepts new connections. */
073: private StreamConnectionNotifier notifier;
074:
075: /** Keeps the information about this server. */
076: private ServiceRecord record;
077:
078: /** Keeps the parent reference to process specific actions. */
079: private GUIImageServer parent;
080:
081: /** Becomes 'true' when this component is finalized. */
082: private boolean isClosed;
083:
084: /** Creates notifier and accepts clients to be processed. */
085: private Thread accepterThread;
086:
087: /** Process the particular client from queue. */
088: private ClientProcessor processor;
089:
090: /** Optimization: keeps the table of data elements to be published. */
091: private final Hashtable dataElements = new Hashtable();
092:
093: /**
094: * Constructs the bluetooth server, but it is initialized
095: * in the different thread to "avoid dead lock".
096: */
097: BTImageServer(GUIImageServer parent) {
098: this .parent = parent;
099:
100: // we have to initialize a system in different thread...
101: accepterThread = new Thread(this );
102: accepterThread.start();
103: }
104:
105: /**
106: * Accepts a new client and send him/her a requested image.
107: */
108: public void run() {
109: boolean isBTReady = false;
110:
111: try {
112: // create/get a local device
113: localDevice = LocalDevice.getLocalDevice();
114:
115: // set we are discoverable
116: if (!localDevice.setDiscoverable(DiscoveryAgent.GIAC)) {
117: // Some implementations always return false, even if
118: // setDiscoverable successful
119: // throw new IOException("Can't set discoverable mode...");
120: }
121:
122: // prepare a URL to create a notifier
123: StringBuffer url = new StringBuffer("btspp://");
124:
125: // indicate this is a server
126: url.append("localhost").append(':');
127:
128: // add the UUID to identify this service
129: url.append(PICTURES_SERVER_UUID.toString());
130:
131: // add the name for our service
132: url.append(";name=Picture Server");
133:
134: // request all of the client not to be authorized
135: // some devices fail on authorize=true
136: url.append(";authorize=false");
137:
138: // create notifier now
139: notifier = (StreamConnectionNotifier) Connector.open(url
140: .toString());
141:
142: // and remember the service record for the later updates
143: record = localDevice.getRecord(notifier);
144:
145: // create a special attribute with images names
146: DataElement base = new DataElement(DataElement.DATSEQ);
147: record.setAttributeValue(IMAGES_NAMES_ATTRIBUTE_ID, base);
148:
149: // remember we've reached this point.
150: isBTReady = true;
151: } catch (Exception e) {
152: System.err.println("Can't initialize bluetooth: " + e);
153: }
154:
155: parent.completeInitialization(isBTReady);
156:
157: // nothing to do if no bluetooth available
158: if (!isBTReady) {
159: return;
160: }
161:
162: // ok, start processor now
163: processor = new ClientProcessor();
164:
165: // ok, start accepting connections then
166: while (!isClosed) {
167: StreamConnection conn = null;
168:
169: try {
170: conn = notifier.acceptAndOpen();
171: } catch (IOException e) {
172: // wrong client or interrupted - continue anyway
173: continue;
174: }
175:
176: processor.addConnection(conn);
177: }
178: }
179:
180: /**
181: * Updates the service record with the information
182: * about the published images availability.
183: * <p>
184: * This method is invoked after the caller has checked
185: * already that the real action should be done.
186: *
187: * @return true if record was updated successfully, false otherwise.
188: */
189: boolean changeImageInfo(String name, boolean isPublished) {
190: // ok, get the record from service
191: DataElement base = record
192: .getAttributeValue(IMAGES_NAMES_ATTRIBUTE_ID);
193:
194: // check the corresponding DataElement object is created already
195: DataElement de = (DataElement) dataElements.get(name);
196:
197: // if no, then create a new DataElement that describes this image
198: if (de == null) {
199: de = new DataElement(DataElement.STRING, name);
200: dataElements.put(name, de);
201: }
202:
203: // we know this data element has DATSEQ type
204: if (isPublished) {
205: base.addElement(de);
206: } else {
207: if (!base.removeElement(de)) {
208: System.err.println("Error: item was not removed for: "
209: + name);
210:
211: return false;
212: }
213: }
214:
215: record.setAttributeValue(IMAGES_NAMES_ATTRIBUTE_ID, base);
216:
217: try {
218: localDevice.updateRecord(record);
219: } catch (ServiceRegistrationException e) {
220: System.err.println("Can't update record now for: " + name);
221:
222: return false;
223: }
224:
225: return true;
226: }
227:
228: /**
229: * Destroy a work with bluetooth - exits the accepting
230: * thread and close notifier.
231: */
232: void destroy() {
233: isClosed = true;
234:
235: // finalize notifier work
236: if (notifier != null) {
237: try {
238: notifier.close();
239: } catch (IOException e) {
240: } // ignore
241: }
242:
243: // wait for acceptor thread is done
244: try {
245: accepterThread.join();
246: } catch (InterruptedException e) {
247: } // ignore
248:
249: // finalize processor
250: if (processor != null) {
251: processor.destroy(true);
252: }
253:
254: processor = null;
255: }
256:
257: /**
258: * Reads the image name from the specified connection
259: * and sends this image through this connection, then
260: * close it after all.
261: */
262: private void processConnection(StreamConnection conn) {
263: // read the image name first
264: String imgName = readImageName(conn);
265:
266: // check this image is published and get the image file name
267: imgName = parent.getImageFileName(imgName);
268:
269: // load image data into buffer to be send
270: byte[] imgData = getImageData(imgName);
271:
272: // send image data now
273: sendImageData(imgData, conn);
274:
275: // close connection and good-bye
276: try {
277: conn.close();
278: } catch (IOException e) {
279: } // ignore
280: }
281:
282: /** Send image data. */
283: private void sendImageData(byte[] imgData, StreamConnection conn) {
284: if (imgData == null) {
285: return;
286: }
287:
288: OutputStream out = null;
289:
290: try {
291: out = conn.openOutputStream();
292: out.write(imgData.length >> 8);
293: out.write(imgData.length & 0xff);
294: out.write(imgData);
295: out.flush();
296: } catch (IOException e) {
297: System.err.println("Can't send image data: " + e);
298: }
299:
300: // close output stream anyway
301: if (out != null) {
302: try {
303: out.close();
304: } catch (IOException e) {
305: } // ignore
306: }
307: }
308:
309: /** Reads image name from specified connection. */
310: private String readImageName(StreamConnection conn) {
311: String imgName = null;
312: InputStream in = null;
313:
314: try {
315: in = conn.openInputStream();
316:
317: int length = in.read(); // 'name' length is 1 byte
318:
319: if (length <= 0) {
320: throw new IOException("Can't read name length");
321: }
322:
323: byte[] nameData = new byte[length];
324: length = 0;
325:
326: while (length != nameData.length) {
327: int n = in.read(nameData, length, nameData.length
328: - length);
329:
330: if (n == -1) {
331: throw new IOException("Can't read name data");
332: }
333:
334: length += n;
335: }
336:
337: imgName = new String(nameData);
338: } catch (IOException e) {
339: System.err.println(e);
340: }
341:
342: // close input stream anyway
343: if (in != null) {
344: try {
345: in.close();
346: } catch (IOException e) {
347: } // ignore
348: }
349:
350: return imgName;
351: }
352:
353: /** Reads images data from MIDlet archive to array. */
354: private byte[] getImageData(String imgName) {
355: if (imgName == null) {
356: return null;
357: }
358:
359: InputStream in = getClass().getResourceAsStream(imgName);
360:
361: // read image data and create a byte array
362: byte[] buff = new byte[1024];
363: ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
364:
365: try {
366: while (true) {
367: int length = in.read(buff);
368:
369: if (length == -1) {
370: break;
371: }
372:
373: baos.write(buff, 0, length);
374: }
375: } catch (IOException e) {
376: System.err.println("Can't get image data: imgName="
377: + imgName + " :" + e);
378:
379: return null;
380: }
381:
382: return baos.toByteArray();
383: }
384:
385: /**
386: * Organizes the queue of clients to be processed,
387: * processes the clients one by one until destroyed.
388: */
389: private class ClientProcessor implements Runnable {
390: private Thread processorThread;
391: private Vector queue = new Vector();
392: private boolean isOk = true;
393:
394: ClientProcessor() {
395: processorThread = new Thread(this );
396: processorThread.start();
397: }
398:
399: public void run() {
400: while (!isClosed) {
401: // wait for new task to be processed
402: synchronized (this ) {
403: if (queue.size() == 0) {
404: try {
405: wait();
406: } catch (InterruptedException e) {
407: System.err.println("Unexpected exception: "
408: + e);
409: destroy(false);
410:
411: return;
412: }
413: }
414: }
415:
416: // send the image to specified connection
417: StreamConnection conn;
418:
419: synchronized (this ) {
420: // may be awaked by "destroy" method.
421: if (isClosed) {
422: return;
423: }
424:
425: conn = (StreamConnection) queue.firstElement();
426: queue.removeElementAt(0);
427: processConnection(conn);
428: }
429: }
430: }
431:
432: /** Adds the connection to queue and notifies the thread. */
433: void addConnection(StreamConnection conn) {
434: synchronized (this ) {
435: queue.addElement(conn);
436: notify();
437: }
438: }
439:
440: /** Closes the connections and . */
441: void destroy(boolean needJoin) {
442: StreamConnection conn;
443:
444: synchronized (this ) {
445: notify();
446:
447: while (queue.size() != 0) {
448: conn = (StreamConnection) queue.firstElement();
449: queue.removeElementAt(0);
450:
451: try {
452: conn.close();
453: } catch (IOException e) {
454: } // ignore
455: }
456: }
457:
458: // wait until dispatching thread is done
459: try {
460: processorThread.join();
461: } catch (InterruptedException e) {
462: } // ignore
463: }
464: }
465: } // end of class 'BTImageServer' definition
|