/* * ============================================================================ * The Apache Software License, Version 1.1 * ============================================================================ * * Copyright (C) 2002 The Apache Software Foundation. All rights reserved. * * Redistribution and use in source and binary forms, with or without modifica- * tion, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. The end-user documentation included with the redistribution, if any, must * include the following acknowledgment: "This product includes software * developed by SuperBonBon Industries (http://www.sbbi.net/)." * Alternately, this acknowledgment may appear in the software itself, if * and wherever such third-party acknowledgments normally appear. * * 4. The names "UPNPLib" and "SuperBonBon Industries" must not be * used to endorse or promote products derived from this software without * prior written permission. For written permission, please contact * info@sbbi.net. * * 5. Products derived from this software may not be called * "SuperBonBon Industries", nor may "SBBI" appear in their name, * without prior written permission of SuperBonBon Industries. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * APACHE SOFTWARE FOUNDATION OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT,INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLU- * DING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * This software consists of voluntary contributions made by many individuals * on behalf of SuperBonBon Industries. For more information on * SuperBonBon Industries, please see <http://www.sbbi.net/>. */ 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 org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; 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; /** * 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 NotificationBroadcasterSupport notifier; private MBeanNotificationInfo[] notifInfo; private Map registeredBeansPerUDN; private Set searchTargets; private int discoveryTimeout; private boolean notifySSDPEvents; private 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[] 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(); 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 registeredBeans = (Set)registeredBeansPerUDN.get( deviceUDN ); ObjectName[] rtrVal = null; if ( registeredBeans != null && registeredBeans.size() > 0 ) { Set copy = new HashSet( registeredBeans ); rtrVal = new ObjectName[copy.size()]; int z = 0; for ( Iterator i = copy.iterator(); i.hasNext(); ) { UPNPServiceMBean srv = (UPNPServiceMBean)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.size() == 0 ) return null; Map copy = new HashMap( registeredBeansPerUDN ); String[] rtrVal = new String[copy.size()]; int z = 0; for ( Iterator i = copy.keySet().iterator(); i.hasNext(); ) { rtrVal[z++] = (String)i.next(); } return rtrVal; } /** * The registered devices search targets * @return a set of search targets */ public Set 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 i = searchTargets.iterator(); i.hasNext(); ) { String st = (String)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 i = registeredBeansPerUDN.values().iterator(); i.hasNext(); ) { Set registeredMBeans = (Set)i.next(); for ( Iterator z = registeredMBeans.iterator(); z.hasNext(); ) { UPNPServiceMBean bean = (UPNPServiceMBean)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 i = searchTargets.iterator(); i.hasNext(); ) { String st = (String)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 registeredBeans = (Set)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 i = registeredBeans.iterator(); i.hasNext(); ) { UPNPServiceMBean bean = (UPNPServiceMBean)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 j = searchTargets.iterator(); j.hasNext(); ) { String st = (String)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 registeredMBeansContainer, String deviceUDN ) throws Exception { List 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(); registeredBeansPerUDN.put( deviceUDN, registeredMBeansContainer ); } List services = device.getServices(); if ( services != null ) { registerServices( device, server, services, registeredMBeansContainer ); } if( childrens != null ) { if ( registerChildDevices ) { for ( Iterator itr = childrens.iterator(); itr.hasNext(); ) { UPNPDevice childDevice = (UPNPDevice)itr.next(); // all childrens of the device are automatically registered register( childDevice, Discovery.ROOT_DEVICES, registeredMBeansContainer, deviceUDN ); } } childrens = null; } } } if( childrens != null ) { for ( Iterator itr = childrens.iterator(); itr.hasNext(); ) { UPNPDevice childDevice = (UPNPDevice)itr.next(); register( childDevice, searchTarget, null, deviceUDN ); } } } private void registerServices( UPNPDevice device, MBeanServer server, List services, Set beansContainer ) throws Exception { for ( Iterator i = services.iterator(); i.hasNext(); ) { UPNPService srv = (UPNPService)i.next(); UPNPServiceMBean mBean = new UPNPServiceMBean( device, srv, null, null ); log.info( "Registering service " + srv.getServiceId() ); server.registerMBean( mBean, mBean.getObjectName() ); beansContainer.add( mBean ); } } }