/* * ============================================================================ * 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.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.URL; import java.security.MessageDigest; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Properties; import javax.management.DynamicMBean; import javax.management.InstanceNotFoundException; import javax.management.IntrospectionException; import javax.management.MBeanInfo; import javax.management.MBeanServer; import javax.management.ObjectName; import javax.management.ReflectionException; import javax.management.modelmbean.ModelMBean; /** * This class can be used to expose a JMX MBean as an UPNP device. * The MBeans methods names and params obtained via the MBeanInfo Object * will be used to create the UPNP device operations set, and the UPNP devie state variables * will be obtained from the MBeans attributes. * STILL A WORK IN PROGRESS MUST BE CONSIDERED AS BETA QUALITY SOFTWARE * doc net.sbbi.upnp.UPNPMBeanDevice.boundAddr sys prop * @author <a href="mailto:superbonbon@sbbi.net">SuperBonBon</a> * @version 1.0 */ public class UPNPMBeanDevice { private static String libVersion = null; static { Properties props = new Properties(); try { props.load( UPNPMBeanDevice.class.getClassLoader().getResourceAsStream( "net/sbbi/upnp/version.properties" ) ); libVersion = props.getProperty( "release.version" ); } catch ( IOException ex ) { ex.printStackTrace(); } } public static final String IMPL_NAME = System.getProperty( "os.name" ) + " UPnP/1.0 SuperBonBon Industries JMX UPNP/" + libVersion; public static int DEFAULT_MAX_AGE = 1800; public static int DEFAULT_TTL = 4; private static InetSocketAddress defaultBindAddr = getUPNPMBeansBoundAddr(); private String uuid; private String internalId; private String deviceInfo; private InetSocketAddress bindAddress; private String location; private boolean rootDevice = true; private boolean started = false; private List childrens = new ArrayList(); private List services = new ArrayList(); // required fields private String vendorDomain; private String deviceType; private String manufacturer; private int deviceVersion; private String friendlyName; private String modelName; // optional fields private URL manufacturerURL; private String modelDescription; private String modelNumber; private URL modelURL; private String serialNumber; private String UPC; private int SSDPAliveDelay = DEFAULT_MAX_AGE; private int SSDPTTL = DEFAULT_TTL; public UPNPMBeanDevice( String deviceType, int deviceVersion, String manufacturer, String friendlyName, String modelName, String internalId ) throws RuntimeException { this( "urn:schemas-upnp-org", deviceType, deviceVersion, manufacturer, friendlyName, modelName, internalId ); } public UPNPMBeanDevice( String vendorDomain, String deviceType, int deviceVersion, String manufacturer, String friendlyName, String modelName, String internalId ) throws RuntimeException { this.vendorDomain = vendorDomain; this.deviceVersion = deviceVersion; this.deviceType = "urn:" + this.vendorDomain + ":device:" + deviceType + ":" + this.deviceVersion; this.manufacturer = manufacturer; this.friendlyName = friendlyName; this.modelName = modelName; this.internalId = internalId; if ( this.internalId == null ) this.internalId = this.deviceType; bindAddress = defaultBindAddr; generateDeviceUUID(); } public void setBindAddress( InetSocketAddress bindAddress ) { this.bindAddress = bindAddress; generateDeviceUUID(); } public InetSocketAddress getBindAddress() { return bindAddress; } public int getSSDPAliveDelay() { return SSDPAliveDelay; } /** * The SSDP alive broadcast message sending delay in seconds, * should be greater than 1800 secs * @param aliveDelay */ public void setSSDPAliveDelay( int aliveDelay ) { if ( aliveDelay < DEFAULT_MAX_AGE ) throw new IllegalArgumentException( "SSDPAliveDelay must be greater than " + DEFAULT_MAX_AGE + " secs" ); SSDPAliveDelay = aliveDelay; } public int getSSDPTTL() { return SSDPTTL; } public void setSSDPTTL(int ssdpttl) { SSDPTTL = ssdpttl; } protected UPNPMBeanService getUPNPMBeanService( String serviceUuid ) { for ( Iterator i = services.iterator(); i.hasNext(); ) { UPNPMBeanService srv = (UPNPMBeanService)i.next(); if ( srv.getServiceUUID().equals( serviceUuid ) ) { return srv; } } return null; } protected List getUPNPMBeanServices() { return services; } protected List getUPNPMBeanChildrens() { return childrens; } protected String getUuid() { return uuid; } protected String getDeviceInfo() { return deviceInfo; } public boolean isStarted() { return started; } protected String getLocation() { return location; } protected String getDeviceType() { return deviceType; } protected boolean isRootDevice() { return rootDevice; } public void addChildMBean( UPNPMBeanDevice device ) { device.rootDevice = false; childrens.add( device ); } public void addService( ModelMBean mbean, ObjectName beanName, MBeanServer targetServer, String serviceId, String serviceType, int serviceVersion ) throws IOException { addService( mbean.getMBeanInfo(), beanName, targetServer, serviceId, serviceType, serviceVersion ); } public void addService( DynamicMBean mbean, ObjectName beanName, MBeanServer targetServer, String serviceId, String serviceType, int serviceVersion ) throws IOException { addService( mbean.getMBeanInfo(), beanName, targetServer, serviceId, serviceType, serviceVersion ); } public void addService( Object mbean, ObjectName beanName, MBeanServer targetServer, String serviceId, String serviceType, int serviceVersion ) throws IOException, IntrospectionException, InstanceNotFoundException, ReflectionException { addService( targetServer.getMBeanInfo( beanName ), beanName, targetServer, serviceId, serviceType, serviceVersion ); } public void addService( MBeanInfo info, ObjectName beanName, MBeanServer targetServer, String serviceId, String serviceType, int serviceVersion ) throws IOException { UPNPMBeanService deviceService = new UPNPMBeanService( uuid, this.vendorDomain, serviceId, serviceType, serviceVersion, info, beanName, targetServer ); // check that there is no duplicate serviceType String newServiceType = deviceService.getServiceType(); for ( Iterator i = services.iterator(); i.hasNext(); ) { UPNPMBeanService srv = (UPNPMBeanService)i.next(); if ( srv.getServiceType().equals( newServiceType ) ) { throw new IOException( "Service type " + serviceType + " for MBeans " + beanName + " is already used by MBeans " + srv.getObjectName() + ", you must use an unique service type" ); } } services.add( deviceService ); } private void generateDeviceUUID() { try { MessageDigest md5 = MessageDigest.getInstance( "MD5" ); // the uuid is based on the device type, the internal id // and the host name md5.update( deviceType.getBytes() ); md5.update( internalId.getBytes() ); md5.update( bindAddress.getHostName().getBytes() ); StringBuffer hexString = new StringBuffer(); byte[] digest = md5.digest(); for (int i=0;i< digest.length; i++ ) { hexString.append( Integer.toHexString( 0xFF & digest[i] ) ); } uuid = hexString.toString().toUpperCase(); } catch ( Exception ex ) { RuntimeException runTimeEx = new RuntimeException( "Unexpected error during MD5 hash creation, check your JRE" ); runTimeEx.initCause( ex ); throw runTimeEx; } } public void start() throws Exception { if ( !started ) { if ( services.size() == 0 ) throw new Exception( "No UPNP service defined" ); deviceInfo = getRootDeviceInfo( bindAddress, uuid ); location = "http://" + bindAddress.getAddress().getHostAddress() + ":" + bindAddress.getPort() + "/" + uuid + "/desc.xml"; UPNPMBeanDevicesRequestsHandler.getInstance( bindAddress ).addUPNPMBeanDevice( this ); UPNPMBeanDevicesDiscoveryHandler.getInstance( bindAddress ).addUPNPMBeanDevice( this ); started = true; } } private static InetSocketAddress getUPNPMBeansBoundAddr() { String boundAdr = System.getProperty( "net.sbbi.upnp.UPNPMBeanDevice.boundAddr" ); InetSocketAddress defaultBoundAddr = null; try { InetAddress adr = null; if ( boundAdr != null ) { adr = InetAddress.getByName( boundAdr ); } else { adr = InetAddress.getLocalHost(); } defaultBoundAddr = new InetSocketAddress( adr, 8895 ); } catch ( IOException ex ) { defaultBoundAddr = new InetSocketAddress( "localhost", 8895 ); } return defaultBoundAddr; } public void stop() throws IOException { if ( started ) { UPNPMBeanDevicesRequestsHandler.getInstance( bindAddress ).removeUPNPMBeanDevice( this ); UPNPMBeanDevicesDiscoveryHandler.getInstance( bindAddress ).removeUPNPMBeanDevice( this ); } } private String getRootDeviceInfo( InetSocketAddress adr, String uuid ) { StringBuffer rtrVal = new StringBuffer(); rtrVal.append( "<?xml version=\"1.0\" ?>\r\n" ); rtrVal.append( "<root xmlns=\"urn:schemas-upnp-org:device-1-0\">\r\n" ); rtrVal.append( "<specVersion><major>1</major><minor>0</minor></specVersion>\r\n" ); rtrVal.append( "<URLBase>http://" ).append( adr.getAddress().getHostAddress() ).append( ":" ).append( adr.getPort() ).append( "</URLBase>\r\n" ); getDeviceInfo( this, rtrVal ); rtrVal.append( "</root>" ); return rtrVal.toString(); } private void getDeviceInfo( UPNPMBeanDevice device, StringBuffer buffer ) { buffer.append( "<device>\r\n" ); buffer.append( "<deviceType>" ).append( device.deviceType ).append( "</deviceType>\r\n" ); buffer.append( "<friendlyName>" ).append( device.friendlyName ).append( "</friendlyName>\r\n" ); buffer.append( "<manufacturer>" ).append( device.manufacturer ).append( "</manufacturer>\r\n" ); if ( device.manufacturerURL != null ) { buffer.append( "<manufacturerURL>" ).append( device.manufacturerURL ).append( "</manufacturerURL>\r\n" ); } if ( device.modelDescription != null ) { buffer.append( "<modelDescription>" ).append( device.modelDescription ).append( "</modelDescription>\r\n" ); } buffer.append( "<modelName>" ).append( device.modelName ).append( "</modelName>\r\n" ); if ( device.modelNumber != null ) { buffer.append( "<modelNumber>" ).append( device.modelNumber ).append( "</modelNumber>\r\n" ); } if ( device.modelURL != null ) { buffer.append( "<modelURL>" ).append( device.modelURL ).append( "</modelURL>\r\n" ); } if ( device.serialNumber != null ) { buffer.append( "<serialNumber>" ).append( device.serialNumber ).append( "</serialNumber>\r\n" ); } buffer.append( "<UDN>uuid:" ).append( device.uuid ).append( "</UDN>\r\n" ); if ( device.UPC != null ) { buffer.append( "<UPC>" ).append( device.serialNumber ).append( "</UPC>\r\n" ); } buffer.append( "<serviceList>\r\n" ); for ( Iterator i = device.services.iterator(); i.hasNext(); ) { UPNPMBeanService srv = (UPNPMBeanService)i.next(); buffer.append( srv.getServiceInfo() ); } buffer.append( "</serviceList>\r\n" ); if ( device.childrens.size() > 0 ) { buffer.append( "<deviceList>\r\n" ); for ( Iterator i = device.childrens.iterator(); i.hasNext(); ) { UPNPMBeanDevice dv = (UPNPMBeanDevice)i.next(); getDeviceInfo( dv, buffer ); } buffer.append( "</deviceList>\r\n" ); } buffer.append( "</device>\r\n" ); } }