package org.red5.server.jmx; /* * RED5 Open Source Flash Server - http://www.osflash.org/red5 * * Copyright (c) 2006-2009 by respective authors (see below). All rights reserved. * * This library is free software; you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free Software * Foundation; either version 2.1 of the License, or (at your option) any later * version. * * This library is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License along * with this library; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ import java.io.File; import java.io.IOException; import java.rmi.ConnectException; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import java.util.HashMap; import java.util.Set; import javax.management.InstanceAlreadyExistsException; import javax.management.MBeanServer; import javax.management.Notification; import javax.management.NotificationListener; import javax.management.ObjectName; import javax.management.StandardMBean; import javax.management.remote.JMXConnectorServer; import javax.management.remote.JMXConnectorServerFactory; import javax.management.remote.JMXServiceURL; import javax.management.remote.rmi.RMIConnectorServer; import javax.rmi.ssl.SslRMIClientSocketFactory; import javax.rmi.ssl.SslRMIServerSocketFactory; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Provides the connection adapters as well as registration and * unregistration of MBeans. * * @author The Red5 Project (red5@osflash.org) * @author Paul Gregoire (mondain@gmail.com) */ public class JMXAgent implements NotificationListener { private static JMXConnectorServer cs; private static boolean enableRmiAdapter; private static boolean startRegistry; private static boolean enableSsl; private static boolean enableMinaMonitor; private static Logger log = LoggerFactory.getLogger(JMXAgent.class); private static MBeanServer mbs; private static String rmiAdapterPort = "9999"; private static String rmiAdapterRemotePort = ""; private static String rmiAdapterHost = "localhost"; private static String remotePasswordProperties; private static String remoteAccessProperties; private static String remoteSSLKeystore; private static String remoteSSLKeystorePass; static { //in the war version the jmx factory is not created before //registration starts ?? so we check for it here and init //if needed if (null == mbs) { mbs = JMXFactory.getMBeanServer(); } } /** * Convenience to remove packages etc from a class name. * * @param className class name to trim * @return trimmed class name */ public static String trimClassName(String className) { if (StringUtils.isNotBlank(className)) { if (className.indexOf('.') != -1) { //strip package stuff className = className.substring(className.lastIndexOf('.') + 1); } } return className; } @SuppressWarnings("unchecked") public static boolean registerMBean(Object instance, String className, Class interfaceClass) { boolean status = false; try { String cName = className; if (cName.indexOf('.') != -1) { cName = cName.substring(cName.lastIndexOf('.')).replaceFirst( "[\\.]", ""); } log.debug("Register name: {}", cName); mbs.registerMBean(new StandardMBean(instance, interfaceClass), new ObjectName(JMXFactory.getDefaultDomain() + ":type=" + cName)); status = true; } catch (InstanceAlreadyExistsException iaee) { log.debug("Already registered: {}", className); } catch (Exception e) { log.error("Could not register the {} MBean. {}", className, e); } return status; } @SuppressWarnings("unchecked") public static boolean registerMBean(Object instance, String className, Class interfaceClass, ObjectName name) { boolean status = false; try { String cName = className; if (cName.indexOf('.') != -1) { cName = cName.substring(cName.lastIndexOf('.')).replaceFirst( "[\\.]", ""); } log.debug("Register name: {}", cName); mbs.registerMBean(new StandardMBean(instance, interfaceClass), name); status = true; } catch (InstanceAlreadyExistsException iaee) { log.debug("Already registered: {}", className); } catch (Exception e) { log.error("Could not register the {} MBean. {}", className, e); } return status; } @SuppressWarnings("unchecked") public static boolean registerMBean(Object instance, String className, Class interfaceClass, String name) { boolean status = false; try { String cName = className; if (cName.indexOf('.') != -1) { cName = cName.substring(cName.lastIndexOf('.')).replaceFirst( "[\\.]", ""); } log.debug("Register name: {}", cName); mbs.registerMBean(new StandardMBean(instance, interfaceClass), new ObjectName(JMXFactory.getDefaultDomain() + ":type=" + cName + ",name=" + name)); status = true; } catch (InstanceAlreadyExistsException iaee) { log.debug("Already registered: {}", className); } catch (Exception e) { log.error("Could not register the {} MBean. {}", className, e); } return status; } /** * Shuts down any instanced connectors. */ public static void shutdown() { log.info("Shutting down JMX agent"); if (null != cs) { try { //stop the connector cs.stop(); } catch (Exception e) { log.error("Exception stopping JMXConnector server {}", e); } } try { //unregister all the currently registered red5 mbeans String domain = JMXFactory.getDefaultDomain(); for (ObjectName oname : (Set<ObjectName>) mbs.queryNames( new ObjectName(domain + ":*"), null)) { log.debug("Bean domain: {}", oname.getDomain()); if (domain.equals(oname.getDomain())) { unregisterMBean(oname); } } } catch (Exception e) { log.error("Exception unregistering mbeans {}", e); } } /** * Unregisters an mbean instance. If the instance is not found or if a failure occurs, false will be returned. * @param oName bean instance * @return true if success; false if instance not found or failure */ public static boolean unregisterMBean(ObjectName oName) { boolean unregistered = false; if (null != oName) { try { if (mbs.isRegistered(oName)) { log.debug("Mbean is registered"); mbs.unregisterMBean(oName); //set flag based on registration status unregistered = !mbs.isRegistered(oName); } else { log.debug("Mbean is not currently registered"); } } catch (Exception e) { log.warn("Exception unregistering mbean {}", e); } } log.debug("leaving unregisterMBean..."); return unregistered; } /** * Updates a named attribute of a registered mbean. * * @param oName object name * @param key key * @param value new value * @return true if success; false othwerwise */ public static boolean updateMBeanAttribute(ObjectName oName, String key, int value) { boolean updated = false; if (null != oName) { try { // update the attribute if (mbs.isRegistered(oName)) { mbs.setAttribute(oName, new javax.management.Attribute( "key", value)); updated = true; } } catch (Exception e) { log.error("Exception updating mbean attribute", e); } } return updated; } /** * Updates a named attribute of a registered mbean. * * @param oName object name * @param key key * @param value new value * @return true if success; false otherwise */ public static boolean updateMBeanAttribute(ObjectName oName, String key, String value) { boolean updated = false; if (null != oName) { try { // update the attribute if (mbs.isRegistered(oName)) { mbs.setAttribute(oName, new javax.management.Attribute( "key", value)); updated = true; } } catch (Exception e) { log.error("Exception updating mbean attribute", e); } } return updated; } public void handleNotification(Notification notification, Object handback) { log.debug("handleNotification {}", notification.getMessage()); } public void init() { //environmental var holder HashMap<String, Object> env = null; if (enableRmiAdapter) { // Create an RMI connector server log.debug("Create an RMI connector server"); // bind the rmi hostname for systems with nat and multiple binded addresses ! System.setProperty("java.rmi.server.hostname", rmiAdapterHost); try { Registry r = null; try { //lookup the registry r = LocateRegistry.getRegistry(Integer .valueOf(rmiAdapterPort)); //ensure we are not already registered with the registry for (String regName : r.list()) { if (regName.equals("red5")) { //unbind connector from rmi registry r.unbind("red5"); } } } catch (RemoteException re) { log.info("RMI Registry server was not found on port " + rmiAdapterPort); //if we didnt find the registry and the user wants it created if (startRegistry) { log.info("Starting an internal RMI registry"); r = LocateRegistry.createRegistry(Integer .valueOf(rmiAdapterPort)); } } JMXServiceURL url = null; // Configure the remote objects exported port for firewalls !! if (StringUtils.isNotEmpty(rmiAdapterRemotePort)) { url = new JMXServiceURL("service:jmx:rmi://" + rmiAdapterHost + ":" + rmiAdapterRemotePort + "/jndi/rmi://" + rmiAdapterHost + ":" + rmiAdapterPort + "/red5"); } else { url = new JMXServiceURL( "service:jmx:rmi:///jndi/rmi://:" + rmiAdapterPort + "/red5"); } log.info("JMXServiceUrl is: {}", url); //if SSL is requested to secure rmi connections if (enableSsl) { // Setup keystore for SSL transparently System.setProperty("javax.net.ssl.keyStore", remoteSSLKeystore); System.setProperty("javax.net.ssl.keyStorePassword", remoteSSLKeystorePass); // Environment map log.debug("Initialize the environment map"); env = new HashMap<String, Object>(); // Provide SSL-based RMI socket factories SslRMIClientSocketFactory csf = new SslRMIClientSocketFactory(); SslRMIServerSocketFactory ssf = new SslRMIServerSocketFactory(); env .put( RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE, csf); env .put( RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE, ssf); } //if authentication is requested if (StringUtils.isNotBlank(remoteAccessProperties)) { //if ssl is not used this will be null if (null == env) { env = new HashMap<String, Object>(); } //check the existance of the files //in the war version the full path is needed File file = new File(remoteAccessProperties); if (!file.exists() && remoteAccessProperties.indexOf(System.getProperty("red5.config_root")) != 0) { log.debug("Access file was not found on path, will prepend config_root"); //pre-pend the system property set in war startup remoteAccessProperties = System .getProperty("red5.config_root") + '/' + remoteAccessProperties; remotePasswordProperties = System .getProperty("red5.config_root") + '/' + remotePasswordProperties; } env.put("jmx.remote.x.access.file", remoteAccessProperties); env.put("jmx.remote.x.password.file", remotePasswordProperties); } // create the connector server cs = JMXConnectorServerFactory.newJMXConnectorServer(url, env, mbs); // add a listener for shutdown cs.addNotificationListener(this, null, null); // Start the RMI connector server log.debug("Start the RMI connector server"); cs.start(); log.info("JMX RMI connector server successfully started"); } catch (ConnectException e) { log .warn("Could not establish RMI connection to port " + rmiAdapterPort + ", please make sure \"rmiregistry\" is running and configured to listen on this port."); } catch (IOException e) { String errMsg = e.getMessage(); if (errMsg.indexOf("NameAlreadyBoundException") != -1) { log .error("JMX connector (red5) already registered, you will need to restart your rmiregistry"); } else { log.error("{}", e); } } catch (Exception e) { log.error("Error in setup of JMX subsystem (RMI connector)", e); } } else { log.info("JMX RMI adapter was not enabled"); } } public void setEnableRmiAdapter(boolean enableRmiAdapter) { JMXAgent.enableRmiAdapter = enableRmiAdapter; } public void setEnableRmiAdapter(String enableRmiAdapterString) { JMXAgent.enableRmiAdapter = enableRmiAdapterString .matches("true|on|yes"); } public void setEnableSsl(boolean enableSsl) { JMXAgent.enableSsl = enableSsl; } public void setEnableSsl(String enableSslString) { JMXAgent.enableSsl = enableSslString.matches("true|on|yes"); } public void setRemoteAccessProperties(String remoteAccessProperties) { JMXAgent.remoteAccessProperties = remoteAccessProperties; } public void setRemotePasswordProperties(String remotePasswordProperties) { JMXAgent.remotePasswordProperties = remotePasswordProperties; } public void setRemoteSSLKeystore(String remoteSSLKeystore) { JMXAgent.remoteSSLKeystore = remoteSSLKeystore; } public void setRemoteSSLKeystorePass(String remoteSSLKeystorePass) { JMXAgent.remoteSSLKeystorePass = remoteSSLKeystorePass; } public void setRmiAdapterRemotePort(String rmiAdapterRemotePort) { JMXAgent.rmiAdapterRemotePort = rmiAdapterRemotePort; } public void setRmiAdapterPort(String rmiAdapterPort) { JMXAgent.rmiAdapterPort = rmiAdapterPort; } public void setRmiAdapterHost(String rmiAdapterHost) { JMXAgent.rmiAdapterHost = rmiAdapterHost; } public void setStartRegistry(boolean startRegistry) { JMXAgent.startRegistry = startRegistry; } public void setEnableMinaMonitor(boolean enableMinaMonitor) { JMXAgent.enableMinaMonitor = enableMinaMonitor; } public void setEnableMinaMonitor(String enableMinaMonitor) { JMXAgent.enableMinaMonitor = enableMinaMonitor.matches("true|on|yes"); } public static boolean isEnableMinaMonitor() { return enableMinaMonitor; } }