/* * ============================================================================ * 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.security.MessageDigest; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import javax.management.AttributeNotFoundException; import javax.management.InstanceNotFoundException; import javax.management.MBeanAttributeInfo; import javax.management.MBeanException; import javax.management.MBeanInfo; import javax.management.MBeanOperationInfo; import javax.management.MBeanParameterInfo; import javax.management.MBeanServer; import javax.management.ObjectName; import javax.management.ReflectionException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import net.sbbi.upnp.services.ServiceStateVariable; import net.sbbi.upnp.services.ServiceStateVariableTypes; /** * Class to expose an JMX Mbean as an UPNP device service, this class shouldn't be used * directly * @author <a href="mailto:superbonbon@sbbi.net">SuperBonBon</a> * @version 1.0 */ public class UPNPMBeanService { private final static Log log = LogFactory.getLog( UPNPMBeanService.class ); private String serviceType; private String serviceId; private String serviceUUID; private String deviceUUID; private String deviceSCDP; private Map operationsStateVariables; private MBeanServer targetServer; private MBeanInfo mbeanInfo; private ObjectName mbeanName; private Class targetBeanClass; protected UPNPMBeanService( String deviceUUID, String vendorDomain, String serviceId, String serviceType, int serviceVersion, MBeanInfo mbeanInfo, ObjectName mbeanName, MBeanServer targetServer ) throws IOException { this.deviceUUID = deviceUUID; if ( serviceId == null ) { serviceId = generateServiceId( mbeanName ); } this.serviceUUID = deviceUUID + "/" + serviceId; this.serviceType = "urn:" + vendorDomain + ":service:" + serviceType + ":" + serviceVersion; this.serviceId = "urn:" + vendorDomain + ":serviceId:" + serviceId; deviceSCDP = getDeviceSSDP( mbeanInfo ); try { targetBeanClass = Class.forName( mbeanInfo.getClassName() ); } catch ( ClassNotFoundException ex ) { IOException ex2 = new IOException( "Unable to find target MBean class " + mbeanInfo.getClassName() ); ex2.initCause( ex ); throw ex2; } this.mbeanName = mbeanName; this.mbeanInfo = mbeanInfo; this.targetServer = targetServer; } protected String getServiceUUID() { return serviceUUID; } protected String getDeviceUUID() { return deviceUUID; } private String generateServiceId( ObjectName mbeanName ) { try { MessageDigest md5 = MessageDigest.getInstance( "MD5" ); // the uuid is based on the device type, the internal id // and the host name md5.update( mbeanName.toString().getBytes() ); StringBuffer hexString = new StringBuffer(); byte[] digest = md5.digest(); for (int i=0;i< digest.length; i++ ) { hexString.append( Integer.toHexString( 0xFF & digest[i] ) ); } return hexString.toString().toUpperCase(); } catch ( Exception ex ) { RuntimeException runTimeEx = new RuntimeException( "Unexpected error during MD5 hash creation, check your JRE" ); runTimeEx.initCause( ex ); throw runTimeEx; } } protected String getServiceInfo() { StringBuffer rtrVal = new StringBuffer(); rtrVal.append( "<service>\r\n" ); rtrVal.append( "<serviceType>" ).append( serviceType ).append( "</serviceType>\r\n" ); rtrVal.append( "<serviceId>" ).append( serviceId ).append( "</serviceId>\r\n" ); rtrVal.append( "<controlURL>" ).append( "/" ).append( serviceUUID ).append( "/control").append( "</controlURL>\r\n" ); rtrVal.append( "<eventSubURL>" ).append( "/" ).append( serviceUUID ).append( "/events" ).append( "</eventSubURL>\r\n" ); rtrVal.append( "<SCPDURL>" ).append( "/" ).append( serviceUUID ).append( "/scpd.xml" ).append( "</SCPDURL>\r\n" ); rtrVal.append( "</service>\r\n" ); return rtrVal.toString(); } protected Map getOperationsStateVariables() { return operationsStateVariables; } protected String getDeviceSCDP() { return deviceSCDP; } protected String getServiceType() { return serviceType; } protected Class getTargetBeanClass() { return targetBeanClass; } protected ObjectName getObjectName() { return mbeanName; } protected Object getAttribute( String attributeName ) throws InstanceNotFoundException, AttributeNotFoundException, ReflectionException, MBeanException { return targetServer.getAttribute( mbeanName, attributeName ); } protected Object invoke( String actionName, Object[] params, String[] signature ) throws ReflectionException, InstanceNotFoundException, MBeanException { return targetServer.invoke( mbeanName, actionName, params, signature ); } private String getDeviceSSDP( MBeanInfo info ) throws IllegalArgumentException { MBeanOperationInfo[] ops = info.getOperations(); MBeanAttributeInfo[] atts = info.getAttributes(); if ( ( ops == null || ops.length == 0 ) && ( atts == null || atts.length == 0 ) ) { throw new IllegalArgumentException( "MBean has no operation and no attribute and cannot be exposed as an UPNP device, provide at least one attribute" ); } Set deployedActionNames = new HashSet(); operationsStateVariables = new HashMap(); StringBuffer rtrVal = new StringBuffer(); rtrVal.append( "<?xml version=\"1.0\" ?>\r\n" ); rtrVal.append( "<scpd xmlns=\"urn:schemas-upnp-org:service-1-0\">\r\n" ); rtrVal.append( "<specVersion><major>1</major><minor>0</minor></specVersion>\r\n" ); if ( ops != null && ops.length > 0 ) { rtrVal.append( "<actionList>\r\n" ); for ( int i = 0; i < ops.length; i++ ) { MBeanOperationInfo op = ops[i]; StringBuffer action = new StringBuffer(); if ( deployedActionNames.contains( op.getName() ) ) { log.debug( "The " + op.getName() + " is allready deplyoed and cannot be reused, skipping operation deployment" ); continue; } action.append( "<action>\r\n" ); action.append( "<name>" ); action.append( op.getName() ); action.append( "</name>\r\n" ); action.append( "<argumentList>\r\n" ); // output argument action.append( "<argument>\r\n" ); action.append( "<name>" ); String outVarName = op.getName() + "_out"; String actionOutDataType = ServiceStateVariable.getUPNPDataTypeMapping( op.getReturnType() ); if ( actionOutDataType == null ) actionOutDataType = ServiceStateVariableTypes.STRING; action.append( outVarName ); action.append( "</name>\r\n" ); action.append( "<direction>out</direction>\r\n" ); action.append( "<relatedStateVariable>" ); action.append( outVarName ); action.append( "</relatedStateVariable>\r\n" ); action.append( "</argument>\r\n" ); // handle now for all input argument boolean nonPrimitiveInputType = false; boolean duplicatedInputVarname = false; Map operationsInputStateVariables = new HashMap(); if ( op.getSignature() != null ) { for ( int z = 0; z < op.getSignature().length; z++ ) { MBeanParameterInfo param = op.getSignature()[z]; // do some sanity checks String actionInDataType = ServiceStateVariable.getUPNPDataTypeMapping( param.getType() ); if ( actionInDataType == null ) { nonPrimitiveInputType = true; log.debug( "The " + param.getType() + " type is not an UPNP compatible data type, use only primitives" ); break; } String inVarName = param.getName(); // check that if the name does allready exists it // has the same type String existing = (String)operationsStateVariables.get( inVarName ); if ( existing != null && !existing.equals( actionInDataType ) ) { String msg = "The operation " + op.getName() + " " + inVarName + " parameter already exists for another method with another data type (" + existing + ") either match the data type or change the parameter name" + " in you MBeanParameterInfo object for this operation"; duplicatedInputVarname = true; log.debug( msg ); break; } action.append( "<argument>\r\n" ); action.append( "<name>" ); operationsInputStateVariables.put( inVarName, actionInDataType ); action.append( inVarName ); action.append( "</name>\r\n" ); action.append( "<direction>in</direction>\r\n" ); action.append( "<relatedStateVariable>" ); action.append( inVarName ); action.append( "</relatedStateVariable>\r\n" ); action.append( "</argument>\r\n" ); } } action.append( "</argumentList>\r\n" ); action.append( "</action>\r\n" ); // finally the action is only added to the UPNP SSDP if no problems have been detected // with the input parameters type and names. if ( !nonPrimitiveInputType && !duplicatedInputVarname ) { operationsStateVariables.putAll( operationsInputStateVariables ); operationsStateVariables.put( outVarName, actionOutDataType ); rtrVal.append( action.toString() ); deployedActionNames.add( op.getName() ); } } rtrVal.append( "</actionList>\r\n" ); } else { rtrVal.append( "<actionList/>\r\n" ); } // now append the operation created state vars rtrVal.append( "<serviceStateTable>\r\n" ); for ( Iterator i = operationsStateVariables.keySet().iterator(); i.hasNext(); ) { String name = (String)i.next(); String type = (String)operationsStateVariables.get( name ); rtrVal.append( "<stateVariable sendEvents=\"no\">\r\n" ); rtrVal.append( "<name>" ); rtrVal.append( name ); rtrVal.append( "</name>\r\n" ); rtrVal.append( "<dataType>" ); rtrVal.append( type ); rtrVal.append( "</dataType>\r\n" ); rtrVal.append( "</stateVariable>\r\n" ); } if ( atts != null && atts.length > 0 ) { for ( int i = 0; i < atts.length; i++ ) { MBeanAttributeInfo att = atts[i]; if ( att.isReadable() ) { rtrVal.append( "<stateVariable sendEvents=\"no\">\r\n" ); rtrVal.append( "<name>" ); rtrVal.append( att.getName() ); rtrVal.append( "</name>\r\n" ); rtrVal.append( "<dataType>" ); String stateVarType = ServiceStateVariable.getUPNPDataTypeMapping( att.getType() ); if ( stateVarType == null ) stateVarType = ServiceStateVariableTypes.STRING; rtrVal.append( stateVarType ); rtrVal.append( "</dataType>\r\n" ); rtrVal.append( "</stateVariable>\r\n" ); } } } rtrVal.append( "</serviceStateTable>\r\n" ); rtrVal.append( "</scpd>" ); return rtrVal.toString(); } public MBeanInfo getMBeanInfo() { return mbeanInfo; } }