blob: 281abbaa68dc26d547f13efb432464c3916a8e88 [file] [log] [blame]
Francesco Furfariec7e1752006-10-02 13:37:04 +00001/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
Francesco Furfarid8bdb642006-04-04 23:33:40 +00009 *
Francesco Furfariec7e1752006-10-02 13:37:04 +000010 * http://www.apache.org/licenses/LICENSE-2.0
Francesco Furfarid8bdb642006-04-04 23:33:40 +000011 *
Francesco Furfariec7e1752006-10-02 13:37:04 +000012 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
Francesco Furfarid8bdb642006-04-04 23:33:40 +000018 */
19
20package org.apache.felix.upnp.basedriver.export;
21
22
23import java.io.File;
24import java.util.Enumeration;
25import java.util.Hashtable;
26import java.util.Properties;
27import java.util.Vector;
28
29import org.cybergarage.upnp.Device;
30import org.cybergarage.upnp.DeviceList;
31import org.cybergarage.upnp.ServiceList;
32import org.cybergarage.upnp.UPnP;
33
34import org.osgi.framework.BundleException;
35import org.osgi.framework.Constants;
36import org.osgi.framework.Filter;
37import org.osgi.framework.InvalidSyntaxException;
38import org.osgi.framework.ServiceEvent;
39import org.osgi.framework.ServiceListener;
40import org.osgi.framework.ServiceReference;
41import org.osgi.framework.ServiceRegistration;
42import org.osgi.service.upnp.UPnPDevice;
43import org.osgi.service.upnp.UPnPEventListener;
44
45import org.apache.felix.upnp.basedriver.Activator;
46
Francesco Furfarif2a67912006-07-17 17:08:02 +000047/*
Karl Paulsd312acc2007-06-18 20:38:33 +000048* @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
Francesco Furfarif2a67912006-07-17 17:08:02 +000049*/
Francesco Furfarid8bdb642006-04-04 23:33:40 +000050public class ThreadExporter implements Runnable,ServiceListener {
51
52 private boolean end;
53
54 private RootDeviceExportingQueue queueRootDevice;
55
56// private String basePath; twa: redundant
57
58// private File baseFile; twa: redundant
59
60 private Hashtable exportedDevices;
61
62 private boolean listening;
63
64 private class ExportedDeviceInfo{
65 private Device device;
66 private ServiceRegistration serviceRegistration;
67 //private DeviceNode deviceNode;
68
69
70 /**
71 * @param device
72 * @param serviceRegistration
73 * @param deviceNode
74 */
75 private ExportedDeviceInfo(Device device,
76 ServiceRegistration serviceRegistration,
77 DeviceNode deviceNode) {
78 super();
79 this.device = device;
80 this.serviceRegistration = serviceRegistration;
81 //this.deviceNode = deviceNode;
82 }
83
84
85 private Device getDevice() {
86 return this.device;
87 }
88 private ServiceRegistration getServiceRegistration() {
89 return this.serviceRegistration;
90 }
91 /*private DeviceNode getDeviceNode(){
92 return this.deviceNode;
93 }*/
94
95
96 }
97
98 /**
99 *
100 */
101 public ThreadExporter(RootDeviceExportingQueue queue) throws InvalidSyntaxException {
102 end=false;
103 queueRootDevice=queue;
104// basePath="./tmp/device"; twa: redundant
105// baseFile = Activator.bc.getDataFile("./tmp/device"); twa: redundant
106// if(!baseFile.exists()) baseFile.mkdirs(); twa: redundant
107 this.exportedDevices=new Hashtable();
108 setListening(false);
109 UPnP.setEnable(UPnP.USE_ONLY_IPV4_ADDR);
110 }
111 public void run() {
112
113 File osgiRoot = Activator.bc.getDataFile("");
114 if (osgiRoot == null) {
115 Activator.logger.ERROR("Unable to use filesystem");
116 while (true) {
117 try {
118 Activator.bc.getBundle().stop();
119 break;
120 } catch (BundleException e) {
121 e.printStackTrace();
122 try {
123 Thread.sleep(1000);
124 } catch (InterruptedException ex) {
125 ex.printStackTrace();
126 }
127 }
128 }
129 return;
130 }
131
132 ServiceReference rootDevice = null;
133 while (!shouldEnd()) {
134 DeviceNode dn = queueRootDevice.getRootDevice();
135 if (dn == null)
136 continue;
137 rootDevice = dn.getReference();
138 if(!getListening())
139 setListen();
140 Activator.logger.INFO("[Exporter] Exporting device "+ rootDevice.getProperty(UPnPDevice.FRIENDLY_NAME));
141 /*
142 File xml = new File(baseFile,
143 Converter.sanitizeFilename(
144 (String) rootDevice.getProperty(UPnPDevice.UDN)
145 )
146 );
147 if (xml == null)
148 continue;
149 if (!xml.exists())
150 xml.mkdir();
151
152 FileOutputStream fos = null;
153 try {
154 fos = new FileOutputStream(xml.getAbsolutePath()
155 + File.separator + "desc.xml");
156 } catch (FileNotFoundException e) {
157 Activator.logger.log(LogService.LOG_ERROR,"Unable to write:" + xml.getAbsolutePath(), e);
158 continue;
159 }
160 if (fos == null)
161 continue;*/
162 /*
163 * I don't know if the exporting should be make default language of the framework
164 * or without any lanuguages
165 Root r = new Root(rootDevice, context, context
166 .getProperty(Constants.FRAMEWORK_LANGUAGE));
167 */
168
169 synchronized (this) {
170 //Root r = new Root(rootDevice, Activator.bc, null);
171 /*
172 * Now that I have XML I'm going to exporting device
173 * so I have to avoid rece condition with deregistration
174 * of the same device
175 */
176 /*
177 try {
178 r.writeXML(fos);
179 } catch (IOException e) {
180 e.printStackTrace();
181 continue;
182 }
183 if(writeXMLService(xml.getAbsolutePath(),r.getRootDevice())){
184 Device d = null;
185 try {
186 d = new Device(xml.getAbsolutePath()
187 + File.separator + "desc.xml");
188 } catch (InvalidDescriptionException e) {
189 e.printStackTrace();
190 }*/
191 Device d = BuildDevice.createCyberLinkDevice(dn.getReference());
192 if (d != null) {
193 if(!bindInvokes(d,rootDevice)){
194 Activator.logger.DEBUG("Unable to find all the sub device or to set action listener");
195 continue;
196 }
197 ServiceRegistration listenReg = bindSubscribe(d);
198 if(listenReg==null){
199 Activator.logger.DEBUG("Unable to set action listener event listener");
200 continue;
201 }
202 //makeIcons(r.getRootDevice(),xml.getAbsolutePath());
203 d.start();
204 exportedDevices.put(
205 rootDevice.getProperty(UPnPDevice.UDN),
206 new ExportedDeviceInfo(d,listenReg,dn)
207 );
208 }
209 //}
210 }
211 }
212 }
213
214 /**
215 *
216 */
217 private void setListen() {
218 {
219 try {
220 Activator.bc.addServiceListener(
221 this,
222 // fixed by Matteo and Francesco 21/9/04
223 "(&("+Constants.OBJECTCLASS+"="+UPnPDevice.class.getName()+")"
224 + "(" + UPnPDevice.UPNP_EXPORT +"=*))"
225 );
226 } catch (InvalidSyntaxException ingnore) {}
227 }
228 }
229 /*
230 /**
231 * @param upnpDev
232 * @param refDev
233 *
234 private void makeIcons(
235 org.apache.felix.upnpbase.export.xml.Device upnpDev,
236 String path) {
237 Icon[] icons = upnpDev.getIcons();
238 if(icons!=null){
239 byte[] buf = new byte[512];
240 for (int i = 0; i < icons.length; i++) {
241 try {
242 String icoPath = path+icons[i].getUrl().replace('/',File.separatorChar);
243 InputStream is = icons[i].getInputStream();
244 Converter.makeParentPath(icoPath);
245 FileOutputStream fos = new FileOutputStream(icoPath);
246 int n=is.read(buf,0,buf.length);
247 while(n>0){
248 fos.write(buf,0,n);
249 n=is.read(buf,0,buf.length);
250 }
251 } catch (IOException e) {
252 e.printStackTrace();
253 }
254 }
255 }
256 org.apache.felix.upnpbase.export.xml.Device[] devs = upnpDev.getDevices();
257 if(devs==null)
258 return;
259 for (int i = 0; i < devs.length; i++) {
260 makeIcons(devs[i],path);
261 }
262 }*/
263 /**
264 * This method is used to connect all the Action that are shown to UPnP world
265 * by CyberLink UPnP Device to the real implementation that is conatined iniside
266 * the OSGi service.
267 * This method will connect even all the subdevice of te given OSGi device.
268 *
269 * @param d CyberLink Device that will be used associated to the OSGi Device
270 * @param rootDevice ServiceReference to the OSGi Device that will be used as
271 * implementation of the CyberLink Device
272 * @return true if and only if the binding off all the action of all the children
273 * device is done succesfully
274 */
275 private boolean bindInvokes(Device d, ServiceReference rootDevice) {
276 bindInvoke(d,rootDevice);
277 //Activator.bc.ungetService(rootDevice);
278 ServiceReference[] childs = null;
279 try {
280 childs = Activator.bc.getServiceReferences(
281 UPnPDevice.class.getName(),
282 "("+UPnPDevice.PARENT_UDN+"="+rootDevice.getProperty(UPnPDevice.UDN)+")"
283 );
284 } catch (InvalidSyntaxException e) {
285 e.printStackTrace();
286 }
287 String[] childsUDN = (String[]) rootDevice.getProperty(UPnPDevice.CHILDREN_UDN);
288 if((childs==null)&&(childsUDN==null)){
289 return true;
290 }else if((childs==null)||(childsUDN==null)){
291 return false;
292 }else if(childs.length==childsUDN.length){
293 /*--- your code ---
294 for (int i = 0; i < childs.length; i++) {
295 if(!bindInvokes(d,childs[i]))
296 return false;
297 }
298 ----- your code end ---*/
299 /*--- twa code ---*/
300 DeviceList dl = d.getDeviceList();
301 for (int i = 0; i < childs.length; i++) {
302 Device dev = (Device)dl.elementAt(i);
303 if(!bindInvokes(dev,childs[i]))
304 return false;
305 }
306 /*----- twa code end ---*/
307
308 return true;
309 }else{
310 return false;
311 }
312
313 }
314 /*
315 /**
316 * @param path
317 *
318 private boolean writeXMLService(String path,org.apache.felix.upnpbase.export.xml.Device d) {
319 Vector v = new Vector();
320 v.add(d);
321 while(v.size()!=0){
322 d=(org.apache.felix.upnpbase.export.xml.Device) v.elementAt(0);
323 v.remove(0);
324 Service[] servs = d.getServices();
325 if(servs==null)
326 continue;
327 for (int i = 0; i < servs.length; i++) {
328 FileOutputStream fos;
329 String xmlPath=path+servs[i].getScpdURL().replace('/',File.separatorChar);
330 if(!Converter.makeParentPath(xmlPath)) return false;
331 try {
332 fos = new FileOutputStream(xmlPath);
333 } catch (FileNotFoundException e) {
334 e.printStackTrace();
335 return false;
336 }
337 try {
338 servs[i].writeXML(fos);
339 fos.close();
340 } catch (IOException e) {
341 e.printStackTrace();
342 }
343 }
344
345 org.apache.felix.upnpbase.export.xml.Device[] subs = d.getDevices();
346 if(subs==null){
347 return true;
348 }else{
349 for(int i = 0; i < subs.length;i++){
350 v.add(subs[i]);
351 }
352 }
353 }
354 return true;
355 }*/
356 /**
357 * This method add an UPnPEventListener Service to the OSGi Framework so that
358 * the Base Driver can notify all the event listener registered on the CyberLink
359 * UPnP device from the UPnP World.
360 *
361 * @param d Device of CyberLink that will be notified by the changing of the StateVariable
362 * that happen on the OSGi World
363 * @return ServiceRegistration of the new registered service.
364 */
365 private ServiceRegistration bindSubscribe(Device d) {
366 ExporterUPnPEventListener eventer = new ExporterUPnPEventListener(d);
367 Properties p = new Properties();
368
369 StringBuffer sb = new StringBuffer("(|");
370 Vector v = new Vector();
371 v.add(d);
372 Device current;
373 while (v.size() != 0) {
374 current = (Device) v.elementAt(0);
375 v.remove(0);
376 DeviceList dl = current.getDeviceList();
377 for (int i = 0; i < dl.size(); i++) {
378 v.add(dl.elementAt(i));
379 }
380 sb.append("(").append(UPnPDevice.ID).append("=").append(
381 current.getUDN()).append(")");
382 }
383 sb.append(")");
384 Filter f = null;
385 try {
386 f = Activator.bc.createFilter(sb.toString());
387 } catch (InvalidSyntaxException e) {
388 e.printStackTrace();
389 return null;
390 }
391 if (f != null) p.put(UPnPEventListener.UPNP_FILTER, f);
392
393 return Activator.bc.registerService(UPnPEventListener.class.getName(), eventer, p);
394 }
395
396 /**
397 * This method do the real connection between OSGi UPnP Service action and
398 * CyberLink UPnP Device action
399 *
400 * @param upnpDev the CyberLink UPnP Device object that will be connected
401 * @param osgiDev the ServiceReference to OSGi UPnP Service that will be connected to
402 * CyberLink UPnP as implementation of the Action
403 * @return true if and only if the binding off all the action is done succesfully
404 */
405 private boolean bindInvoke(Device upnpDev,ServiceReference osgiDev) {
406 ServiceList sl = upnpDev.getServiceList();
407 int l=sl.size();
408 for (int i = 0; i < l; i++) {
409 sl.getService(i).setActionListener(
410 new GeneralActionListener(
411 osgiDev,
412 sl.getService(i).getServiceID()
413 )
414 );
415 }
416 return true;
417 }
418
419 /**
420 *
421 */
422 public synchronized void cleanUp() {
423 Activator.logger.INFO("Cleaning...");
424
425 Enumeration keys;
426
427 Activator.logger.INFO("Removing temporary listener....");
428 keys=exportedDevices.keys();
429 while (keys.hasMoreElements()) {
430 ServiceRegistration sr = ((ExportedDeviceInfo)
431 exportedDevices.get(keys.nextElement())).getServiceRegistration();
432 sr.unregister();
433 }
434 Activator.logger.INFO("Done");
435
436 Activator.logger.INFO("Removing device....");
437 keys=exportedDevices.keys();
438 while (keys.hasMoreElements()) {
439 Device dev = ((ExportedDeviceInfo)
440 exportedDevices.get(keys.nextElement())).getDevice();
441 dev.stop();
442 }
443 Activator.logger.INFO("Done");
444 }
445
446 private synchronized boolean shouldEnd() {
447 return end;
448 }
449
450 public synchronized void end() {
451 end = true;
452 queueRootDevice.addRootDevice(null);
453 }
454 /**
455 * @see org.osgi.framework.ServiceListener#serviceChanged(org.osgi.framework.ServiceEvent)
456 */
457 public void serviceChanged(ServiceEvent event) {
458 switch(event.getType()){
459 case ServiceEvent.REGISTERED:break;
460
461 case ServiceEvent.MODIFIED:
462 case ServiceEvent.UNREGISTERING:{
463 this.unexportDevice(event.getServiceReference());
464 if(exportedDevices.size()==0){
465 Activator.bc.removeServiceListener(this);
466 setListening(false);
467 }
468 }break;
469 }
470 }
471 /**
472 * @param b
473 */
474 private synchronized void setListening(boolean b) {
475 listening=b;
476 }
477
478 private synchronized boolean getListening(){
479 return listening;
480 }
481
482 /**
483 * @param property
484 */
485 private synchronized void unexportDevice(ServiceReference dev) {
486 String udn=(String) dev.getProperty(UPnPDevice.PARENT_UDN);
487 if(udn==null){
488 ExportedDeviceInfo edi =
489 (ExportedDeviceInfo) exportedDevices.get(
490 dev.getProperty(UPnPDevice.UDN)
491 );
492 Device d = edi.getDevice();
493 if(d!=null) {
494 Activator.logger.INFO("[Exporter] removing device:" +d.getFriendlyName());
495 d.stop();
496 exportedDevices.remove(d.getUDN());
497 }
498 ServiceRegistration srListener=edi.getServiceRegistration();
499 if(srListener!=null) srListener.unregister();
500
501 }else{
502 ServiceReference[] servs=null;
503 try {
504 servs = Activator.bc.getServiceReferences(
505 UPnPDevice.class.getName(),
506 "("+UPnPDevice.UDN+"="+udn+")"
507 );
508 } catch (InvalidSyntaxException ignored) {}
509 if(servs==null) return;
510 this.unexportDevice(servs[0]);
511 }
512 }
513}