package org.rhq.plugins.jmx.util; import java.io.File; import java.io.FilenameFilter; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import javax.management.remote.JMXServiceURL; import org.rhq.core.domain.configuration.Configuration; import org.rhq.core.domain.configuration.PropertySimple; import org.rhq.core.pluginapi.inventory.InvalidPluginConfigurationException; import org.rhq.core.system.ProcessInfo; import org.rhq.plugins.jmx.JMXComponent; import org.rhq.plugins.jmx.JMXDiscoveryComponent; import org.mc4j.ems.connection.ConnectionFactory; import org.mc4j.ems.connection.local.LocalVMFinder; import org.mc4j.ems.connection.local.LocalVirtualMachine; 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.J2SE5ConnectionTypeDescriptor; import org.mc4j.ems.connection.support.metadata.LocalVMTypeDescriptor; /** * A factory that can construct an EMS {@link ConnectionProvider} for a JMX Server Resource from that Resource's plugin * configuration. * * @author Ian Springer */ public class ConnectionProviderFactory { public static ConnectionProvider createConnectionProvider(Configuration pluginConfig, ProcessInfo process, File tempDir) throws Exception { String connectionTypeDescriptorClassName = pluginConfig.getSimple(JMXDiscoveryComponent.CONNECTION_TYPE) .getStringValue(); if (connectionTypeDescriptorClassName.equals("LocalVMTypeDescriptor")) { connectionTypeDescriptorClassName = LocalVMTypeDescriptor.class.getName(); } Class<?> connectionTypeDescriptorClass; try { connectionTypeDescriptorClass = Class.forName(connectionTypeDescriptorClassName); } catch (ClassNotFoundException e) { throw new InvalidPluginConfigurationException("Invalid connection type - class [" + connectionTypeDescriptorClassName + "] not found."); } if (!(ConnectionTypeDescriptor.class.isAssignableFrom(connectionTypeDescriptorClass))) { throw new InvalidPluginConfigurationException("Invalid connection type - class [" + connectionTypeDescriptorClassName + "] does not implement the " + ConnectionTypeDescriptor.class.getName() + " interface."); } ConnectionTypeDescriptor connectionTypeDescriptor; try { connectionTypeDescriptor = (ConnectionTypeDescriptor) connectionTypeDescriptorClass.newInstance(); } catch (Exception e) { throw new RuntimeException("Failed to instantiate connection type descriptor of type [" + connectionTypeDescriptorClassName + "].", e); } ConnectionSettings settings = new ConnectionSettings(); settings.initializeConnectionType(connectionTypeDescriptor); // Set principal and credentials. String principal = pluginConfig.getSimpleValue(JMXComponent.PRINCIPAL_CONFIG_PROP, null); settings.setPrincipal(principal); String credentials = pluginConfig.getSimpleValue(JMXComponent.CREDENTIALS_CONFIG_PROP, null); settings.setCredentials(credentials); if (connectionTypeDescriptor instanceof LocalVMTypeDescriptor) { // NOTE (ips, 01/19/12): This is not very reliable for long-term management of a JVM, since it uses the // command line from the time the JVM was originally discovered, which may have changed. String commandLine = pluginConfig.getSimpleValue(JMXDiscoveryComponent.COMMAND_LINE_CONFIG_PROPERTY, null); if (commandLine == null) { throw new InvalidPluginConfigurationException("A command line is required for the " + connectionTypeDescriptorClassName + " connection type."); } Map<Integer, LocalVirtualMachine> vms = LocalVMFinder.getManageableVirtualMachines(); LocalVirtualMachine targetVm = null; if (vms != null) { for (LocalVirtualMachine vm : vms.values()) { if (vm.getCommandLine().equals(commandLine)) { targetVm = vm; break; } } } if (targetVm == null) { // This could just be because the JVM is not currently running. throw new Exception("JVM with command line [" + commandLine + "] not found."); } String vmId = String.valueOf(targetVm.getVmid()); settings.setServerUrl(vmId); } else if (connectionTypeDescriptor instanceof J2SE5ConnectionTypeDescriptor) { // Connect via JMX Remoting, using the JVM Attach API to start up a JMX Remoting Agent if necessary. String jmxConnectorAddress = getJmxConnectorAddress(pluginConfig, process); settings.setServerUrl(jmxConnectorAddress); } else { // Handle internal connections (InternalVMTypeDescriptor) (i.e. the RHQ plugin container's own JVM), as // well as miscellaneous types of remote connections - WebSphere, WebLogic, etc. String connectorAddress = pluginConfig.getSimpleValue(JMXDiscoveryComponent.CONNECTOR_ADDRESS_CONFIG_PROPERTY, null); if (connectorAddress == null) { throw new InvalidPluginConfigurationException("A connector address is required for the " + connectionTypeDescriptorClassName + " connection type."); } settings.setServerUrl(connectorAddress); String installURI = pluginConfig.getSimpleValue(JMXDiscoveryComponent.INSTALL_URI, null); settings.setLibraryURI(installURI); } addAdditionalJarsToConnectionSettings(settings, pluginConfig); return createConnectionProvider(settings, tempDir); } private static ConnectionProvider createConnectionProvider(ConnectionSettings settings, File tempDir) { settings.getControlProperties().setProperty(ConnectionFactory.COPY_JARS_TO_TEMP, String.valueOf(Boolean.TRUE)); settings.getControlProperties().setProperty(ConnectionFactory.JAR_TEMP_DIR, tempDir.getAbsolutePath()); ConnectionFactory connectionFactory = new ConnectionFactory(); connectionFactory.discoverServerClasses(settings); return connectionFactory.getConnectionProvider(settings); } private static void addAdditionalJarsToConnectionSettings(ConnectionSettings settings, Configuration pluginConfig) { List<File> additionalEntries = getAdditionalJarsFromConfig(pluginConfig); if (additionalEntries == null || additionalEntries.size() == 0) { return; // nothing to do, there are no additional entries to add } // get the setting's current list of classpath entries - we are going to add to these List<File> settingsEntries = settings.getClassPathEntries(); if (settingsEntries == null) { settingsEntries = new ArrayList<File>(); } // append the additional entries to the end of the setting's current entries settingsEntries.addAll(additionalEntries); // now that we've appended our additional jars, tell the connection settings about the new list settings.setClassPathEntries(settingsEntries); } private static String getJmxConnectorAddress(Configuration pluginConfig, ProcessInfo process) throws Exception { String connectorAddress = pluginConfig.getSimpleValue(JMXDiscoveryComponent.CONNECTOR_ADDRESS_CONFIG_PROPERTY, null); if (connectorAddress == null) { // No JMX connector address defined - try to connect via Attach API. if (process == null) { throw new Exception("Could not find java process for JVM."); } JMXServiceURL jmxServiceURL = JvmUtility.extractJMXServiceURL(process); if (jmxServiceURL == null) { throw new Exception("Could not obtain JMX service URL via Attach API for JVM [" + process + "]."); } connectorAddress = jmxServiceURL.toString(); } return connectorAddress; } /** * Examines the plugin configuration and if it defines additional classpath entries, this * will return a list of files that point to all the jars that need to be added to a classloader * to support the managed JMX resource. * * Note: this is package static scoped so the resource component can use this method. * * @param pluginConfiguration * * @return list of files pointing to additional jars; will be empty if no additional jars are to be added */ public static List<File> getAdditionalJarsFromConfig(Configuration pluginConfiguration) { List<File> jarFiles = new ArrayList<File>(); // get the plugin config setting that contains comma-separated list of files/dirs to additional jars // if no additional classpath entries are specified, we'll return an empty list PropertySimple prop = pluginConfiguration.getSimple(JMXDiscoveryComponent.ADDITIONAL_CLASSPATH_ENTRIES); if (prop == null || prop.getStringValue() == null || prop.getStringValue().trim().length() == 0) { return jarFiles; } String[] paths = prop.getStringValue().trim().split(","); if (paths == null || paths.length == 0) { return jarFiles; } // Get all additional classpath entries which can be listed as jar file names or directories. // If a directory has ".jar" at the end, all jar files found in that directory will be added // as class path entries. final class JarFilenameFilter implements FilenameFilter { public boolean accept(File dir, String name) { return name.endsWith(".jar"); } } for (String path : paths) { path = path.trim(); if (path.length() > 0) { if (path.endsWith("*.jar")) { path = path.substring(0, path.length() - 5); File dir = new File(path); File[] jars = dir.listFiles(new JarFilenameFilter()); if (jars != null && jars.length > 0) { jarFiles.addAll(Arrays.asList(jars)); } } else { File pathFile = new File(path); jarFiles.add(pathFile); } } } return jarFiles; } }