/*
* 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.upnp;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import javax.management.ListenerNotFoundException;
import javax.management.MBeanInfo;
import javax.management.MBeanServer;
import javax.management.MBeanServerInvocationHandler;
import javax.management.MBeanServerNotification;
import javax.management.Notification;
import javax.management.NotificationEmitter;
import javax.management.NotificationListener;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXServiceURL;
import net.sbbi.upnp.jmx.UPNPDiscovery;
import net.sbbi.upnp.jmx.UPNPDiscoveryMBean;
import net.sbbi.upnp.jmx.UPNPMBeanDevice;
import net.sbbi.upnp.jmx.UPNPServiceMBean;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* JMX connector server, this connector can be used to expose all deployed MBeans on an MBeans Server as UPNP
* devices</br> You can use an object implementing the {@link net.sbbi.upnp.jmx.upnp.UPNPMBeanBuilder} interface do
* define which beans can be deployed as UPNP devices.</br> Look at the UPNP_MBEANS_BUILDER,
* EXPOSE_UPNP_DEVICES_AS_MBEANS and EXPOSE_MBEANS_AS_UPNP_DEVICES vars for more info about connector specific settings.
*
* @author <a href="mailto:superbonbon@sbbi.net">SuperBonBon</a>
* @version 1.0
*/
public class UPNPConnectorServer extends JMXConnectorServer implements NotificationListener {
private final static Log log = LogFactory.getLog(UPNPConnectorServer.class);
/**
* Environement key used to define the {@link net.sbbi.upnp.jmx.upnp.UPNPMBeanBuilder} used to select MBeans to
* deploy as UPNP devices Key value must be an object UPNPMBeanBuilder instance. When no implemntation is provided,
* the default {@link net.sbbi.upnp.jmx.upnp.UPNPMBeanBuilderImpl} will be use.
*/
public final static String UPNP_MBEANS_BUILDER = UPNPConnectorServer.class.getName() + ".upnpbeans.builder";
/**
* Environement key do define if the connector can also expose as MBeans all UPNP devices services on the network.
* Key value must be Boolean.TRUE or Boolean.FALSE, default to Boolean.FALSE
*/
public final static String EXPOSE_UPNP_DEVICES_AS_MBEANS = UPNPConnectorServer.class.getName() + ".upnpdevices.as.mbeans";
/**
* Integer key to define the discovery timeout (in ms) of UPNP devices on the network when the
* EXPOSE_UPNP_DEVICES_AS_MBEANS env key is set to Boolean.TRUE
*/
public final static String EXPOSE_UPNP_DEVICES_AS_MBEANS_TIMEOUT = UPNPConnectorServer.class.getName() + ".upnpdevices.as.mbeans.timeout";
/**
* Environement key when EXPOSE_UPNP_DEVICES_AS_MBEANS is set to true, will define SSDP messages will be handled by
* the connector, if set to true new devices joining the network will be automatically exposed as MBeans Key value
* must be Boolean.TRUE or Boolean.FALSE, default to Boolean.FALSE
*/
public final static String HANDLE_SSDP_MESSAGES = UPNPConnectorServer.class.getName() + ".upnpdevices.as.mbeans.ssdp";
/**
* Environnment key to define if the MBeans registered into the connector MBeans server can be exposed as UPNP
* devices. Key value must be Boolean.TRUE or Boolean.FALSE, default to Boolean.TRUE
*/
public final static String EXPOSE_MBEANS_AS_UPNP_DEVICES = UPNPConnectorServer.class.getName() + ".mbeans.as.upnpdevices";
/**
* When set to true all MBeans registred into the MBeans server prior to the connector registration will be exposed
* as UPNP devices Default to Boolean.FALSE
*/
public final static String EXPOSE_EXISTING_MBEANS_AS_UPNP_DEVICES = UPNPConnectorServer.class.getName() + ".existing.mbeans.as.upnpdevices";
private final JMXServiceURL serviceURL;
private final Map<String, ?> env;
private final InetSocketAddress sktAddress;
private UPNPMBeanBuilder builder;
private Boolean exposeUPNPAsMBeans;
private Boolean exposeMBeansAsUPNP;
private Boolean exposeExistingMBeansAsUPNP;
private Boolean handleSSDPMessages;
private final Object STOP_PROCESS = new Object();
private final Map<String, UPNPMBeanDevice> registeredMBeans = Collections.synchronizedMap(new HashMap<String, UPNPMBeanDevice>());
private ObjectName discoveryBeanName;
public UPNPConnectorServer(MBeanServer server, JMXServiceURL serviceURL, Map<String, ?> env) throws IOException {
super(server);
this.serviceURL = serviceURL;
this.env = env;
// TODO implement an JMXConnector for security (such as basic http auth)? problems with the UPNP protocol itself
sktAddress = new InetSocketAddress(InetAddress.getByName(serviceURL.getHost()), serviceURL.getPort());
builder = (UPNPMBeanBuilder) env.get(UPNP_MBEANS_BUILDER);
if (builder == null) {
builder = new UPNPMBeanBuilderImpl();
}
exposeUPNPAsMBeans = (Boolean) env.get(EXPOSE_UPNP_DEVICES_AS_MBEANS);
if (exposeUPNPAsMBeans == null)
exposeUPNPAsMBeans = Boolean.FALSE;
exposeMBeansAsUPNP = (Boolean) env.get(EXPOSE_MBEANS_AS_UPNP_DEVICES);
if (exposeMBeansAsUPNP == null)
exposeMBeansAsUPNP = Boolean.TRUE;
handleSSDPMessages = (Boolean) env.get(HANDLE_SSDP_MESSAGES);
if (handleSSDPMessages == null)
handleSSDPMessages = Boolean.FALSE;
exposeExistingMBeansAsUPNP = (Boolean) env.get(EXPOSE_EXISTING_MBEANS_AS_UPNP_DEVICES);
if (exposeExistingMBeansAsUPNP == null)
exposeExistingMBeansAsUPNP = Boolean.FALSE;
if (!exposeMBeansAsUPNP.booleanValue() && !exposeUPNPAsMBeans.booleanValue() && !exposeExistingMBeansAsUPNP.booleanValue()) {
throw new IOException("Useless UPNPConnectorServer since nothing will be deployed, unregister it");
}
}
public JMXServiceURL getAddress() {
return serviceURL;
}
public Map<String, Object> getAttributes() {
return Collections.unmodifiableMap(env);
}
public boolean isActive() {
return false;
}
public void start() throws IOException {
MBeanServer server = getMBeanServer();
if (exposeMBeansAsUPNP.booleanValue()) {
try {
ObjectName delegate = new ObjectName("JMImplementation:type=MBeanServerDelegate");
NotificationEmitter emmiter = MBeanServerInvocationHandler.newProxyInstance(server, delegate, NotificationEmitter.class, false);
// register for MBeans registration
emmiter.addNotificationListener(this, null, this);
} catch (Exception ex) {
IOException ioEx = new IOException("UPNPConnector start error");
ioEx.initCause(ex);
throw ioEx;
}
}
if (exposeUPNPAsMBeans.booleanValue()) {
int timeout = 2500;
if (env.containsKey(EXPOSE_UPNP_DEVICES_AS_MBEANS_TIMEOUT)) {
timeout = ((Integer) env.get(EXPOSE_UPNP_DEVICES_AS_MBEANS_TIMEOUT)).intValue();
}
try {
discoveryBeanName = new ObjectName("UPNPLib discovery:name=Discovery MBean_" + this.hashCode());
UPNPDiscoveryMBean bean = new UPNPDiscovery(timeout, handleSSDPMessages.booleanValue(), true);
server.registerMBean(bean, discoveryBeanName);
} catch (Exception ex) {
IOException ioEx = new IOException("Error occured during MBeans discovery");
ioEx.initCause(ex);
throw ioEx;
}
}
if (exposeExistingMBeansAsUPNP.booleanValue()) {
int c = 0;
Set<ObjectName> objectInstances = super.getMBeanServer().queryNames(null, null);
for (Iterator<ObjectName> i = objectInstances.iterator(); i.hasNext();) {
ObjectName name = i.next();
MBeanServerNotification not = new MBeanServerNotification(MBeanServerNotification.REGISTRATION_NOTIFICATION, this, c++, name);
handleNotification(not, this);
}
}
}
public void stop() throws IOException {
MBeanServer server = getMBeanServer();
IOException error = null;
if (exposeMBeansAsUPNP.booleanValue()) {
try {
ObjectName delegate = new ObjectName("JMImplementation:type=MBeanServerDelegate");
NotificationEmitter emmiter = MBeanServerInvocationHandler.newProxyInstance(server, delegate, NotificationEmitter.class, false);
emmiter.removeNotificationListener(this, null, this);
} catch (Exception ex) {
// MX4J throws an unexpected ListenerNotFoundException with jre 1.5.06.. works nice with sun JMX impl
if (!(ex instanceof ListenerNotFoundException)) {
IOException ioEx = new IOException("UPNPConnector stop error");
ioEx.initCause(ex);
error = ioEx;
}
}
synchronized (STOP_PROCESS) {
// now stop all the remaining Devices
for (Iterator<UPNPMBeanDevice> i = registeredMBeans.values().iterator(); i.hasNext();) {
UPNPMBeanDevice dv = i.next();
try {
dv.stop();
} catch (IOException ex) {
log.error("Error during UPNPMBean device stop", ex);
}
}
registeredMBeans.clear();
}
}
if (exposeUPNPAsMBeans.booleanValue()) {
try {
server.unregisterMBean(discoveryBeanName);
} catch (Exception ex) {
IOException ioEx = new IOException("Error occured during MBeans discovery");
ioEx.initCause(ex);
throw ioEx;
}
}
if (error != null) {
throw error;
}
}
public void handleNotification(Notification notification, Object handBack) {
if (notification.getType().equals(MBeanServerNotification.REGISTRATION_NOTIFICATION)) {
MBeanServerNotification regNot = (MBeanServerNotification) notification;
MBeanServer srv = getMBeanServer();
ObjectName name = regNot.getMBeanName();
try {
ObjectInstance objIn = srv.getObjectInstance(name);
String className = objIn.getClassName();
// do not expose as UPN, UPNP devices exposed as MBeans ( class UPNPServiceMBean purpose )
if (className.equals(UPNPServiceMBean.class.getName()))
return;
if (builder.select(name, className)) {
MBeanInfo info = srv.getMBeanInfo(name);
UPNPMBeanDevice dv = builder.buildUPNPMBean(getMBeanServer(), objIn, info);
if (dv != null) {
dv.setBindAddress(sktAddress);
dv.start();
registeredMBeans.put(name.toString(), dv);
}
}
} catch (Exception ex) {
log.error("Error during UPNP Mbean device " + name.toString() + " creation", ex);
}
} else if (notification.getType().equals(MBeanServerNotification.UNREGISTRATION_NOTIFICATION)) {
MBeanServerNotification regNot = (MBeanServerNotification) notification;
String beanName = regNot.getMBeanName().toString();
synchronized (STOP_PROCESS) {
UPNPMBeanDevice dv = registeredMBeans.get(beanName);
if (dv != null) {
try {
dv.stop();
} catch (Exception ex) {
log.error("Error during UPNPMBean device stop", ex);
}
registeredMBeans.remove(beanName);
}
}
}
}
}