/* * This software copyright by various authors including the RPTools.net * development team, and licensed under the LGPL Version 3 or, at your * option, any later version. * * Portions of this software were originally covered under the Apache * Software License, Version 1.1 or Version 2.0. * * See the file LICENSE elsewhere in this distribution for license details. */ package net.sbbi.upnp.jmx; import java.net.URL; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import javax.management.ListenerNotFoundException; import javax.management.MBeanNotificationInfo; import javax.management.MBeanServer; import javax.management.MalformedObjectNameException; import javax.management.Notification; import javax.management.NotificationBroadcaster; import javax.management.NotificationBroadcasterSupport; import javax.management.NotificationFilter; import javax.management.NotificationListener; import javax.management.ObjectName; import net.sbbi.upnp.Discovery; import net.sbbi.upnp.DiscoveryAdvertisement; import net.sbbi.upnp.DiscoveryEventHandler; import net.sbbi.upnp.devices.UPNPDevice; import net.sbbi.upnp.devices.UPNPRootDevice; import net.sbbi.upnp.services.UPNPService; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * MBean to discover UPNP devices on the network and register the devices service as UPNPServiceMBean objects during the * MBean registration. The registered UPNPServiceMBean will also be automatically unregistered when the device is * leaving the network ( if notifySSDPEvents constructor param is set to true ) or when the UPNPDiscoveryMBean is * unregistered from teh MBeans server. * * @author <a href="mailto:superbonbon@sbbi.net">SuperBonBon</a> * @version 1.0 */ public class UPNPDiscovery implements DiscoveryEventHandler, UPNPDiscoveryMBean, NotificationBroadcaster { private final static Log log = LogFactory.getLog(UPNPDiscovery.class); private MBeanServer server; private final NotificationBroadcasterSupport notifier; private final MBeanNotificationInfo[] notifInfo; private final Map<String, Set<UPNPServiceMBean>> registeredBeansPerUDN; private final Set<String> searchTargets; private int discoveryTimeout; private final boolean notifySSDPEvents; private final boolean registerChildDevices; private long ssdpAliveSequenceNumber = 0; private long ssdpByeByeSequenceNumber = 0; /** * Main constructor, will discover all devices types * * @param discoveryTimeout * devices discoverytimeout in MS, 0 for default timeout, increase this value if devices are not * responding * @param notifySSDPEvents * boolean to indicate if the MBean should broadcast JMX UPNPDiscoveryNotifications when an matching * device is joining or leaving the network. * @param registerChildDevices * when set to true, discovered device child devices services will also be exposed as UPNPServiceMBean * objects */ public UPNPDiscovery(int discoveryTimeout, boolean notifySSDPEvents, boolean registerChildDevices) { this.registerChildDevices = registerChildDevices; this.notifySSDPEvents = notifySSDPEvents; this.discoveryTimeout = discoveryTimeout; if (this.discoveryTimeout == 0) { this.discoveryTimeout = Discovery.DEFAULT_TIMEOUT; } notifier = new NotificationBroadcasterSupport(); registeredBeansPerUDN = new HashMap<String, Set<UPNPServiceMBean>>(); String[] types = new String[] { SSDP_ALIVE_NOTIFICATION, SSDP_BYEBYE_NOTIFICATION }; notifInfo = new MBeanNotificationInfo[] { new MBeanNotificationInfo(types, Notification.class.getName(), "SSDP UPNP events notifications") }; searchTargets = new HashSet<String>(); searchTargets.add(Discovery.ROOT_DEVICES); } /** * Discover devices of a given type * * @param searchTargets * a list of devices types URI (I.E : urn:schemas-upnp-org:device:WANDevice:1) that should be handled, * list delimited by commas * @param discoveryTimeout * devices discoverytimeout in MS, 0 for default timeout, increase this value if devices are not * responding * @param notifySSDPEvents * boolean to indicate if the MBean should broadcast JMX UPNPDiscoveryNotifications when an matching * device is joining or leaving the network * @param registerChildDevices * when set to true, discovered device child devices services will also be exposed as UPNPServiceMBean * objects */ public UPNPDiscovery(String searchTargets, int discoveryTimeout, boolean notifySSDPEvents, boolean registerChildDevices) { this(discoveryTimeout, notifySSDPEvents, registerChildDevices); String[] targets = searchTargets.split(","); this.searchTargets.clear(); for (int i = 0; i < targets.length; i++) { this.searchTargets.add(targets[i]); } } /** * Discover devices of a given type * * @param searchTargets * a list of devices types URI (I.E : urn:schemas-upnp-org:device:WANDevice:1) that should be handled. * All discovered device children services will also be automatically registered as UPNPServiceMBean. * @param discoveryTimeout * devices discoverytimeout in MS, 0 for default timeout, increase this value if devices are not * responding * @param notifySSDPEvents * boolean to indicate if the MBean should broadcast JMX UPNPDiscoveryNotifications when an matching * device is joining or leaving the network * @param registerChildDevices * when set to true, discovered device child devices services will also be exposed as UPNPServiceMBean * objects */ public UPNPDiscovery(String[] searchTargets, int discoveryTimeout, boolean notifySSDPEvents, boolean registerChildDevices) { this(discoveryTimeout, notifySSDPEvents, registerChildDevices); this.searchTargets.clear(); for (int i = 0; i < searchTargets.length; i++) { this.searchTargets.add(searchTargets[i]); } } /** * Computes an array of object names of registered UPNPServiceMBeans for a given UPNP device UDN * * @param deviceUDN * the UPNP device UDN ( unique id on the network ) * @return an array of object names or null if not matchs found for the given UDN * @throws MalformedObjectNameException * if an object name cannot be computed for an UPNPServiceMBean */ public ObjectName[] getRegisteredUPNPServiceMBeans(String deviceUDN) throws MalformedObjectNameException { Set<UPNPServiceMBean> registeredBeans = registeredBeansPerUDN.get(deviceUDN); ObjectName[] rtrVal = null; if (registeredBeans != null && registeredBeans.size() > 0) { Set<UPNPServiceMBean> copy = new HashSet<UPNPServiceMBean>(registeredBeans); rtrVal = new ObjectName[copy.size()]; int z = 0; for (Iterator<UPNPServiceMBean> i = copy.iterator(); i.hasNext();) { UPNPServiceMBean srv = i.next(); rtrVal[z++] = srv.getObjectName(); } } return rtrVal; } /** * The list of registered devices UDN, the returned UDN can be used with the getRegisteredUPNPServiceMBeans(String * deviceUDN) method to retreive UDN bound UPNPServiceMBean object names * * @return a string array of UDN or null if no UPNP device services registered as UPNPServiceMBean */ public String[] getRegisteredUPNPServiceMBeansUDNs() { if (registeredBeansPerUDN.isEmpty()) return null; Map<String, Set<UPNPServiceMBean>> copy = new HashMap<String, Set<UPNPServiceMBean>>(registeredBeansPerUDN); String[] rtrVal = new String[copy.size()]; int z = 0; for (Iterator<String> i = copy.keySet().iterator(); i.hasNext();) { rtrVal[z++] = i.next(); } return rtrVal; } /** * The registered devices search targets * * @return a set of search targets */ public Set<String> getSearchTargets() { return Collections.unmodifiableSet(searchTargets); } public void addNotificationListener(NotificationListener listener, NotificationFilter filter, Object callback) throws IllegalArgumentException { notifier.addNotificationListener(listener, filter, callback); } public MBeanNotificationInfo[] getNotificationInfo() { if (notifySSDPEvents) { return notifInfo; } return null; } public void removeNotificationListener(NotificationListener listener) throws ListenerNotFoundException { notifier.removeNotificationListener(listener); } public void postDeregister() { } public void postRegister(Boolean arg0) { } public void preDeregister() throws Exception { if (notifySSDPEvents) { for (Iterator<String> i = searchTargets.iterator(); i.hasNext();) { String st = i.next(); DiscoveryAdvertisement.getInstance().unRegisterEvent(DiscoveryAdvertisement.EVENT_SSDP_ALIVE, st, this); DiscoveryAdvertisement.getInstance().unRegisterEvent(DiscoveryAdvertisement.EVENT_SSDP_BYE_BYE, st, this); } } synchronized (registeredBeansPerUDN) { for (Iterator<Set<UPNPServiceMBean>> i = registeredBeansPerUDN.values().iterator(); i.hasNext();) { Set<UPNPServiceMBean> registeredMBeans = i.next(); for (Iterator<UPNPServiceMBean> z = registeredMBeans.iterator(); z.hasNext();) { UPNPServiceMBean bean = z.next(); server.unregisterMBean(bean.getObjectName()); } } } registeredBeansPerUDN.clear(); } public ObjectName preRegister(MBeanServer server, ObjectName objectname) throws Exception { this.server = server; discoverDevices(discoveryTimeout); if (notifySSDPEvents) { for (Iterator<String> i = searchTargets.iterator(); i.hasNext();) { String st = i.next(); DiscoveryAdvertisement.getInstance().registerEvent(DiscoveryAdvertisement.EVENT_SSDP_ALIVE, st, this); DiscoveryAdvertisement.getInstance().registerEvent(DiscoveryAdvertisement.EVENT_SSDP_BYE_BYE, st, this); } } return objectname; } public void eventSSDPAlive(String usn, String udn, String nt, String maxAge, URL location) { if (registeredBeansPerUDN.get(udn) == null) { // new device... if (searchTargets.contains(Discovery.ROOT_DEVICES) || searchTargets.contains(nt)) { try { UPNPRootDevice newDevice = new UPNPRootDevice(location, maxAge, null, usn, udn); log.info("Registering new device " + newDevice.getModelName() + " at " + location); register(newDevice, nt, null, null); UPNPDiscoveryNotification notif = new UPNPDiscoveryNotification(UPNPDiscoveryMBean.SSDP_ALIVE_NOTIFICATION, this, ssdpAliveSequenceNumber++, System.currentTimeMillis()); notif.setLocation(location); notif.setNt(nt); notif.setUdn(udn); notif.setUsn(usn); notif.setUPNPServiceMBeans(this.getRegisteredUPNPServiceMBeans(udn)); notifier.sendNotification(notif); } catch (Exception ex) { log.error("Error during new device " + location + " registration", ex); } } } } public void eventSSDPByeBye(String usn, String udn, String nt) { synchronized (registeredBeansPerUDN) { Set<UPNPServiceMBean> registeredBeans = registeredBeansPerUDN.get(udn); if (registeredBeans != null) { UPNPDiscoveryNotification notif = new UPNPDiscoveryNotification(UPNPDiscoveryMBean.SSDP_BYEBYE_NOTIFICATION, this, ssdpByeByeSequenceNumber++, System.currentTimeMillis()); notif.setNt(nt); notif.setUdn(udn); notif.setUsn(usn); try { notif.setUPNPServiceMBeans(this.getRegisteredUPNPServiceMBeans(udn)); } catch (MalformedObjectNameException ex) { log.error("Error during UPNPServiceMBean unregistration notification", ex); } log.info("Device " + usn + " shutdown"); for (Iterator<UPNPServiceMBean> i = registeredBeans.iterator(); i.hasNext();) { UPNPServiceMBean bean = i.next(); try { server.unregisterMBean(bean.getObjectName()); } catch (Exception ex) { log.error("Error during UPNPServiceMBean unregistration", ex); } } registeredBeansPerUDN.remove(udn); notifier.sendNotification(notif); } } } public void discoverDevices(int timeout) throws Exception { // lookup for all root devices UPNPRootDevice[] dev = Discovery.discover(timeout, Discovery.ROOT_DEVICES); if (dev != null) { for (int i = 0; i < dev.length; i++) { for (Iterator<String> j = searchTargets.iterator(); j.hasNext();) { String st = j.next(); register(dev[i], st, null, null); } } } else { log.info("No devices found on the network"); } } private void register(UPNPDevice device, String searchTarget, Set<UPNPServiceMBean> registeredMBeansContainer, String deviceUDN) throws Exception { List<UPNPDevice> childrens = device.getTopLevelChildDevices(); if (searchTarget.equals(Discovery.ROOT_DEVICES) || device.getDeviceType().equals(searchTarget)) { synchronized (registeredBeansPerUDN) { if (deviceUDN == null) { deviceUDN = device.getUDN(); } log.info("Registering UPNP device " + device.getDeviceType() + " " + device.getUDN() + " services"); if (registeredMBeansContainer == null) { registeredMBeansContainer = new HashSet<UPNPServiceMBean>(); registeredBeansPerUDN.put(deviceUDN, registeredMBeansContainer); } List<UPNPService> services = device.getServices(); if (services != null) { registerServices(device, server, services, registeredMBeansContainer); } if (childrens != null) { if (registerChildDevices) { for (Iterator<UPNPDevice> itr = childrens.iterator(); itr.hasNext();) { UPNPDevice childDevice = itr.next(); // all childrens of the device are automatically registered register(childDevice, Discovery.ROOT_DEVICES, registeredMBeansContainer, deviceUDN); } } childrens = null; } } } if (childrens != null) { for (Iterator<UPNPDevice> itr = childrens.iterator(); itr.hasNext();) { UPNPDevice childDevice = itr.next(); register(childDevice, searchTarget, null, deviceUDN); } } } private void registerServices(UPNPDevice device, MBeanServer server, List<UPNPService> services, Set<UPNPServiceMBean> beansContainer) throws Exception { for (Iterator<UPNPService> i = services.iterator(); i.hasNext();) { UPNPService srv = i.next(); UPNPServiceMBean mBean = new UPNPServiceMBean(device, srv, null, null); log.info("Registering service " + srv.getServiceId()); server.registerMBean(mBean, mBean.getObjectName()); beansContainer.add(mBean); } } }