/*
* ============================================================================
* 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 java.util.logging.Level;
import java.util.logging.Logger;
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;
/**
* 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 Logger log = Logger.getLogger( UPNPDiscovery.class.getName() );
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.log( Level.SEVERE, "Error during new device {0} registration", location);
}
}
}
}
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.severe( "Error during UPNPServiceMBean unregistration notification" );
}
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.severe( "Error during UPNPServiceMBean unregistration" );
}
}
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 );
}
}
}