/* * RHQ Management Platform * Copyright (C) 2005-2009 Red Hat, Inc. * All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation version 2 of the License. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ package org.jboss.on.common.jbossas; import java.io.File; import java.util.Properties; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.mc4j.ems.connection.ConnectionFactory; import org.mc4j.ems.connection.EmsConnection; import org.mc4j.ems.connection.settings.ConnectionSettings; import org.mc4j.ems.connection.support.ConnectionProvider; import org.mc4j.ems.connection.support.metadata.ConnectionTypeDescriptor; import org.mc4j.ems.connection.support.metadata.InternalVMTypeDescriptor; import org.rhq.core.domain.configuration.Configuration; import org.rhq.core.domain.configuration.PropertySimple; /** * Shared helper class to connect to a remote JMX server. * * @author Heiko W. Rupp */ public class JmxConnectionHelper { public static final String CONNECTOR_DESCRIPTOR_TYPE = "connectorDescriptorType"; public static final String CONNECTOR_ADDRESS = "connectorAddress"; public static final String CONNECTOR_PRINCIPAL = "connectorPrincipal"; public static final String CONNECTOR_CREDENTIALS = "connectorCredentials"; public static final String JBOSS_HOME_DIR = "jbossHomeDir"; private static final Log log = LogFactory.getLog(JmxConnectionHelper.class); private static EmsConnection connection; private static Configuration configuration; private static final String JNP_DISABLE_DISCOVERY_JNP_INIT_PROP = "jnp.disableDiscovery"; /** * This is the timeout for the initial connection to the MBeanServer that is made by the AS ResourceComponent's * start method. */ private static final int JNP_TIMEOUT = 30 * 1000; // 30 seconds /** * This is the timeout for MBean attribute gets/sets and operations invoked on the remote MBeanServer. * NOTE: This timeout comes into play if the JBossAS instance has gone down since the original JNP connection was made. */ private static final int JNP_SO_TIMEOUT = 15 * 1000; // 15 seconds /** * Controls the dampening of connection error stack traces in an attempt to control spam to the log file. Each time * a connection error is encountered, this will be incremented. When the connection is finally established, this * will be reset to zero. */ private static int consecutiveConnectionErrors; private boolean copyConnectionLibraries; private File tmpDir; /** * Constructs a new connection helper. * * @param copyConnectionLibraries whether to copy the libraries need for the connection so that * the ems classloader doesn't block the application access to them. * @param tmpDir the temporary directory to use when copying the libraries */ public JmxConnectionHelper(boolean copyConnectionLibraries, File tmpDir) { this.copyConnectionLibraries = copyConnectionLibraries; this.tmpDir = tmpDir; } /** * Obtain an EmsConnection for the passed connection properties. The properties will be retained. * To create a connection with different properties, use this method again with a different set * of properties. * @param config Configuration properties for this connection * @return an EmsConnection or null in case of failure * @see #getEmsConnection() */ public EmsConnection getEmsConnection(Configuration config) { EmsConnection emsConnection = null; configuration = config; try { emsConnection = loadConnection(config, copyConnectionLibraries, tmpDir); } catch (Exception e) { log.error("Component attempting to access a connection that could not be loaded"); } return emsConnection; } /** * Obtain an EmsConnection. This will only work if the connection properties have passed * before via a call to {@link #getEmsConnection(Configuration)} * @return an EmsConnection or null in case of failure * @see #getEmsConnection(org.rhq.core.domain.configuration.Configuration) */ public EmsConnection getEmsConnection() { EmsConnection emsConnection = null; if (configuration == null) { throw new RuntimeException("No configuration set"); } try { emsConnection = loadConnection(configuration, copyConnectionLibraries, tmpDir); } catch (Exception e) { log.error("Component attempting to access a connection that could not be loaded"); } return emsConnection; } /** * This is the preferred way to use a connection from within this class; methods should not access the connection * property directly as it may not have been instantiated if the connection could not be made. * <p/> * <p>If the connection has already been established, return the object reference to it. If not, attempt to make a * live connection to the JMX server.</p> * <p/> * <p>If the connection could not be made in the start(org.rhq.core.pluginapi.inventory.ResourceContext) method, * this method will effectively try to load the connection on each attempt to use it. As such, multiple threads may * attempt to access the connection through this means at a time. Therefore, the method has been made synchronized * on instances of the class.</p> * <p/> * <p>If any errors are encountered, this method will log the error, taking into account logic to prevent spamming * the log file. Calling methods should take care to not redundantly log the exception thrown by this method.</p> * * @param pluginConfig * @return live connection to the JMX server; this will not be <code>null</code> * @throws Exception if there are any issues at all connecting to the server */ private static synchronized EmsConnection loadConnection(Configuration pluginConfig, boolean copyConnectionLibraries, File tmpDir) throws Exception { if (connection == null) { try { //Configuration pluginConfig = this.resourceContext.getPluginConfiguration(); ConnectionSettings connectionSettings = new ConnectionSettings(); String connectionTypeDescriptorClass = pluginConfig.getSimple(CONNECTOR_DESCRIPTOR_TYPE) .getStringValue(); PropertySimple serverUrl = pluginConfig.getSimple(CONNECTOR_ADDRESS); connectionSettings.initializeConnectionType((ConnectionTypeDescriptor) Class.forName( connectionTypeDescriptorClass).newInstance()); // if not provided use the default serverUrl if (null != serverUrl) { connectionSettings.setServerUrl(serverUrl.getStringValue()); } connectionSettings.setPrincipal(pluginConfig.getSimpleValue(CONNECTOR_PRINCIPAL, null)); connectionSettings.setCredentials(pluginConfig.getSimpleValue(CONNECTOR_CREDENTIALS, null)); connectionSettings.setLibraryURI(pluginConfig.getSimpleValue(JBOSS_HOME_DIR, null)); if (connectionSettings.getAdvancedProperties() == null) { connectionSettings.setAdvancedProperties(new Properties()); } connectionSettings.getAdvancedProperties().setProperty(ConnectionFactory.USE_CONTEXT_CLASSLOADER, "true"); connectionSettings.getAdvancedProperties().setProperty(JNP_DISABLE_DISCOVERY_JNP_INIT_PROP, "true"); // Make sure the timeout always happens, even if the JBoss server is hung. connectionSettings.getAdvancedProperties().setProperty("jnp.timeout", String.valueOf(JNP_TIMEOUT)); connectionSettings.getAdvancedProperties().setProperty("jnp.sotimeout", String.valueOf(JNP_SO_TIMEOUT)); // Tell EMS "don't bother creating your own classloader, I (the caller of EMS) already created // an isolated classloader for you, with all necessary jars - just use the context classloader // and don't bother creating a new one for yourself" connectionSettings.getAdvancedProperties().setProperty(ConnectionFactory.USE_CONTEXT_CLASSLOADER, Boolean.TRUE.toString()); if (copyConnectionLibraries) { // Tell EMS to make copies of jar files so that the ems classloader doesn't lock // application files (making us unable to update them) Bug: JBNADM-670 connectionSettings.getControlProperties().setProperty(ConnectionFactory.COPY_JARS_TO_TEMP, String.valueOf(Boolean.TRUE)); } // Tell EMS to use the plugin's temp dir, so the PC will be able to clean it up and so an access control // policy can easily be defined for the dir. EMS will use this for connection libraries (i.e. client // jars) if COPY_JARS control prop is true, but it will also use it for the ems-mpl jar no matter what, // so we need to always set it. connectionSettings.getControlProperties().setProperty(ConnectionFactory.JAR_TEMP_DIR, tmpDir.getAbsolutePath()); connectionSettings.getAdvancedProperties().setProperty(InternalVMTypeDescriptor.DEFAULT_DOMAIN_SEARCH, "jboss"); ConnectionFactory connectionFactory = new ConnectionFactory(); connectionFactory.discoverServerClasses(connectionSettings); ConnectionProvider connectionProvider = connectionFactory.getConnectionProvider(connectionSettings); connection = connectionProvider.connect(); connection.loadSynchronous(false); // this loads all the MBeans consecutiveConnectionErrors = 0; if (log.isDebugEnabled()) log.debug("Successfully made connection to the remote server instance"); } catch (Exception e) { // The connection will be established even in the case that the principal cannot be authenticated, // but the connection will not work. That failure seems to come from the call to loadSynchronous after // the connection is established. If we get to this point that an exception was thrown, close any // connection that was made and null it out so we can try to establish it again. if (connection != null) { if (log.isDebugEnabled()) log.debug("Connection created but an exception was thrown. Closing the connection.", e); connection.close(); connection = null; } // Since the connection is attempted each time it's used, failure to connect could result in log // file spamming. Log it once for every 10 consecutive times it's encountered. if (consecutiveConnectionErrors % 10 == 0) { log.warn("Could not establish connection to the instance [" + (consecutiveConnectionErrors + 1) + "] times.", e); } if (log.isDebugEnabled()) log.debug("Could not connect to the instance for resource ", e); consecutiveConnectionErrors++; throw e; } } return connection; } /** * If necessary attempt to close the EMS connection, then set this.connection null. Synchronized ensure we play * well with loadConnection. */ public synchronized void closeConnection() { if (connection != null) { try { connection.close(); } catch (Exception e) { log.error("Error closing EMS connection: " + e); } connection = null; } } }