org.rhq.plugins.jbossas5.ApplicationServerDiscoveryComponent.java Source code

Java tutorial

Introduction

Here is the source code for org.rhq.plugins.jbossas5.ApplicationServerDiscoveryComponent.java

Source

/*
* Jopr Management Platform
* Copyright (C) 2005-2011 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, version 2, as
* published by the Free Software Foundation, and/or the GNU Lesser
* General Public License, version 2.1, also as published by the Free
* Software Foundation.
*
* 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 and the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License
* and the GNU Lesser General Public License along with this program;
* if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.rhq.plugins.jbossas5;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.maven.artifact.versioning.ComparableVersion;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import org.jboss.on.common.jbossas.JBossASDiscoveryUtils;

import org.rhq.core.domain.configuration.Configuration;
import org.rhq.core.domain.configuration.PropertyList;
import org.rhq.core.domain.configuration.PropertyMap;
import org.rhq.core.domain.configuration.PropertySimple;
import org.rhq.core.pluginapi.event.log.LogFileEventResourceComponentHelper;
import org.rhq.core.pluginapi.inventory.ClassLoaderFacet;
import org.rhq.core.pluginapi.inventory.DiscoveredResourceDetails;
import org.rhq.core.pluginapi.inventory.InvalidPluginConfigurationException;
import org.rhq.core.pluginapi.inventory.ManualAddFacet;
import org.rhq.core.pluginapi.inventory.ProcessScanResult;
import org.rhq.core.pluginapi.inventory.ResourceDiscoveryComponent;
import org.rhq.core.pluginapi.inventory.ResourceDiscoveryContext;
import org.rhq.core.pluginapi.util.FileUtils;
import org.rhq.core.system.ProcessInfo;
import org.rhq.plugins.jbossas5.helper.JBossInstallationInfo;
import org.rhq.plugins.jbossas5.helper.JBossInstanceInfo;
import org.rhq.plugins.jbossas5.helper.JBossProductType;
import org.rhq.plugins.jbossas5.helper.JBossProperties;
import org.rhq.plugins.jbossas5.util.JnpConfig;
import org.rhq.plugins.jbossas5.util.ResourceComponentUtils;

/**
 * A Resource discovery component for JBoss application server Resources, which include the following:
 *
 *   JBoss AS, 5.2.0.Beta1 and later
 *   JBoss EAP, 5.0.0.Beta and later
 *   JBoss EWP, 5.0.0.CR1 and later
 *   JBoss SOA-P, 5.0.0.Beta and later
 *
 * @author Ian Springer
 * @author Mark Spritzler
 */
@SuppressWarnings({ "UnusedDeclaration" })
public class ApplicationServerDiscoveryComponent
        implements ResourceDiscoveryComponent, ClassLoaderFacet, ManualAddFacet {
    private static final String JBOSS_SERVICE_XML = "conf" + File.separator + "jboss-service.xml";
    private static final String JBOSS_NAMING_SERVICE_XML = "deploy" + File.separator + "naming-service.xml";
    private static final String ANY_ADDRESS = "0.0.0.0";
    private static final String LOCALHOST = "127.0.0.1";
    private static final String JAVA_HOME_ENV_VAR = "JAVA_HOME";

    private static final Map<JBossProductType, ComparableVersion> MINIMUM_PRODUCT_VERSIONS = new HashMap<JBossProductType, ComparableVersion>(
            4);
    static {
        MINIMUM_PRODUCT_VERSIONS.put(JBossProductType.AS, new ComparableVersion("5.2.0.Beta1"));
        MINIMUM_PRODUCT_VERSIONS.put(JBossProductType.EAP, new ComparableVersion("5.0.0.Beta"));
        MINIMUM_PRODUCT_VERSIONS.put(JBossProductType.EWP, new ComparableVersion("5.0.0.CR1"));
        MINIMUM_PRODUCT_VERSIONS.put(JBossProductType.SOA, new ComparableVersion("5.0.0.Beta"));
    }

    private static final String[] CLIENT_JAR_URLS = new String[] {
            // NOTE: The jbossall-client.jar aggregates a whole bunch of other jars from the client dir via its
            // MANIFEST.MF Class-Path.
            "%clientUrl%/jbossall-client.jar", //
            "%clientUrl%/trove.jar", //
            "%clientUrl%/javassist.jar", //
            "%commonLibUrl%/jboss-security-aspects.jar", //
            "%libUrl%/jboss-managed.jar", //
            "%libUrl%/jboss-metatype.jar", //
            "%libUrl%/jboss-dependency.jar", //
            "%libUrl%/jboss-reflect.jar", //
            // AS 6.0 M1 and later
            "%libUrl%/jboss-classpool.jar", //
            "%libUrl%/jboss-classpool-scoped.jar", //
            // AS 6.0 M4 and later
            "%commonLibUrl%/jboss-as-profileservice.jar", //
            "%libUrl%/jboss-profileservice-spi.jar" //
    };

    private final Log log = LogFactory.getLog(this.getClass());

    public Set<DiscoveredResourceDetails> discoverResources(ResourceDiscoveryContext discoveryContext) {
        log.trace("Discovering JBoss AS 5.x and 6.x Resources...");
        Set<DiscoveredResourceDetails> resources = new HashSet<DiscoveredResourceDetails>();
        DiscoveredResourceDetails inProcessJBossAS = discoverInProcessJBossAS(discoveryContext);
        if (inProcessJBossAS != null) {
            // If we're running inside a JBoss AS JVM, that's the only AS instance we want to discover.
            resources.add(inProcessJBossAS);
        } else {
            // Otherwise, scan the process table for external AS instances.
            resources.addAll(discoverExternalJBossAsProcesses(discoveryContext));
        }
        log.trace("Discovered " + resources.size() + " JBossAS 5.x and 6.x Resources.");
        return resources;
    }

    public DiscoveredResourceDetails discoverResource(Configuration pluginConfig,
            ResourceDiscoveryContext discoveryContext) throws InvalidPluginConfigurationException {
        ProcessInfo processInfo = null;
        String jbossHomeDir = pluginConfig.getSimple(ApplicationServerPluginConfigurationProperties.HOME_DIR)
                .getStringValue();
        JBossInstallationInfo installInfo;
        try {
            installInfo = new JBossInstallationInfo(new File(jbossHomeDir));
        } catch (IOException e) {
            throw new InvalidPluginConfigurationException(e);
        }
        DiscoveredResourceDetails resourceDetails = createResourceDetails(discoveryContext, pluginConfig,
                processInfo, installInfo);
        return resourceDetails;
    }

    @SuppressWarnings("unchecked")
    public List<URL> getAdditionalClasspathUrls(ResourceDiscoveryContext context, DiscoveredResourceDetails details)
            throws Exception {
        Configuration pluginConfig = details.getPluginConfiguration();
        setValuesForUnsetClientJarUrlProperties(pluginConfig);

        List<URL> clientJarUrls = new ArrayList<URL>();
        for (String clientJarUrlString : CLIENT_JAR_URLS) {
            // Substitute values in for any templated plugin config props.
            clientJarUrlString = ResourceComponentUtils.replacePropertyExpressionsInTemplate(clientJarUrlString,
                    pluginConfig);
            URL clientJarUrl = new URL(clientJarUrlString);
            if (isReadable(clientJarUrl)) {
                clientJarUrls.add(clientJarUrl);
            } else {
                log.warn("Client JAR [" + clientJarUrl + "] does not exist or is not readable (note, this JAR "
                        + " may not be required for some app server versions).");
            }
        }

        return clientJarUrls;
    }

    private boolean isReadable(URL url) {
        try {
            InputStream inputStream = url.openStream();
            try {
                inputStream.close();
            } catch (IOException e) {
                log.error("Failed to close input stream for URL [" + url + "].", e);
            }
            return true;
        } catch (IOException e) {
            return false;
        }
    }

    private void setValuesForUnsetClientJarUrlProperties(Configuration pluginConfig) throws MalformedURLException {
        String homeDir = pluginConfig.getSimple(ApplicationServerPluginConfigurationProperties.HOME_DIR)
                .getStringValue();
        URL homeUrl = new File(homeDir).toURI().toURL();

        String clientUrlString = pluginConfig
                .getSimpleValue(ApplicationServerPluginConfigurationProperties.CLIENT_URL, null);
        if (clientUrlString == null) {
            URL clientUrl = new URL(homeUrl, "client");
            pluginConfig
                    .put(new PropertySimple(ApplicationServerPluginConfigurationProperties.CLIENT_URL, clientUrl));
        }

        String libUrlString = pluginConfig.getSimpleValue(ApplicationServerPluginConfigurationProperties.LIB_URL,
                null);
        if (libUrlString == null) {
            URL libUrl = new URL(homeUrl, "lib");
            pluginConfig.put(new PropertySimple(ApplicationServerPluginConfigurationProperties.LIB_URL, libUrl));
        }

        String commonLibUrlString = pluginConfig
                .getSimpleValue(ApplicationServerPluginConfigurationProperties.COMMON_LIB_URL, null);
        if (commonLibUrlString == null) {
            URL commonLibUrl = new URL(homeUrl, "common/lib");
            pluginConfig.put(new PropertySimple(ApplicationServerPluginConfigurationProperties.COMMON_LIB_URL,
                    commonLibUrl));
        }
    }

    private Set<DiscoveredResourceDetails> discoverExternalJBossAsProcesses(
            ResourceDiscoveryContext discoveryContext) {
        Set<DiscoveredResourceDetails> resources = new HashSet<DiscoveredResourceDetails>();
        List<ProcessScanResult> autoDiscoveryResults = discoveryContext.getAutoDiscoveredProcesses();

        for (ProcessScanResult autoDiscoveryResult : autoDiscoveryResults) {
            ProcessInfo processInfo = autoDiscoveryResult.getProcessInfo();
            if (log.isDebugEnabled())
                log.debug("Discovered JBoss AS process: " + processInfo);

            JBossInstanceInfo cmdLine;
            try {
                cmdLine = new JBossInstanceInfo(processInfo);
            } catch (Exception e) {
                log.error("Failed to process JBoss AS command line: " + Arrays.asList(processInfo.getCommandLine()),
                        e);
                continue;
            }

            // Skip it if it's an AS/EAP/SOA-P version we don't support.
            JBossInstallationInfo installInfo = cmdLine.getInstallInfo();
            if (!isSupportedProduct(installInfo)) {
                continue;
            }

            File installHome = new File(cmdLine.getSystemProperties().getProperty(JBossProperties.HOME_DIR));
            File configDir = new File(cmdLine.getSystemProperties().getProperty(JBossProperties.SERVER_HOME_DIR));

            // The config dir might be a symlink - call getCanonicalFile() to resolve it if so, before
            // calling isDirectory() (isDirectory() returns false for a symlink, even if it points at
            // a directory).
            try {
                if (!configDir.getCanonicalFile().isDirectory()) {
                    log.warn("Skipping discovery for JBoss AS process " + processInfo
                            + ", because configuration dir '" + configDir
                            + "' does not exist or is not a directory.");
                    continue;
                }
            } catch (IOException e) {
                log.error("Skipping discovery for JBoss AS process " + processInfo + ", because configuration dir '"
                        + configDir + "' could not be canonicalized.", e);
                continue;
            }

            Configuration pluginConfiguration = discoveryContext.getDefaultPluginConfiguration();

            // TODO? Set the connection type - local or remote

            // Set the required props...
            String jnpURL = getJnpURL(cmdLine, installHome, configDir);
            PropertySimple namingUrlProp = new PropertySimple(
                    ApplicationServerPluginConfigurationProperties.NAMING_URL, jnpURL);
            if (jnpURL == null) {
                namingUrlProp.setErrorMessage("RHQ failed to discover the naming provider URL.");
            }
            pluginConfiguration.put(namingUrlProp);
            pluginConfiguration.put(new PropertySimple(ApplicationServerPluginConfigurationProperties.HOME_DIR,
                    installHome.getAbsolutePath()));
            pluginConfiguration.put(
                    new PropertySimple(ApplicationServerPluginConfigurationProperties.SERVER_HOME_DIR, configDir));

            // Set the optional props...
            pluginConfiguration.put(new PropertySimple(ApplicationServerPluginConfigurationProperties.SERVER_NAME,
                    cmdLine.getSystemProperties().getProperty(JBossProperties.SERVER_NAME)));
            pluginConfiguration.put(new PropertySimple(ApplicationServerPluginConfigurationProperties.BIND_ADDRESS,
                    cmdLine.getSystemProperties().getProperty(JBossProperties.BIND_ADDRESS)));

            JBossASDiscoveryUtils.UserInfo userInfo = JBossASDiscoveryUtils.getJmxInvokerUserInfo(configDir);
            if (userInfo != null) {
                pluginConfiguration.put(new PropertySimple(ApplicationServerPluginConfigurationProperties.PRINCIPAL,
                        userInfo.getUsername()));
                pluginConfiguration.put(new PropertySimple(
                        ApplicationServerPluginConfigurationProperties.CREDENTIALS, userInfo.getPassword()));
            }

            String javaHome = processInfo.getEnvironmentVariable(JAVA_HOME_ENV_VAR);
            if (javaHome == null && log.isDebugEnabled()) {
                log.warn("Unable to determine the JAVA_HOME environment variable for the JBoss AS process - "
                        + " the Agent is probably running as a user that does not have access to the AS process's "
                        + " environment.");
            }
            pluginConfiguration
                    .put(new PropertySimple(ApplicationServerPluginConfigurationProperties.JAVA_HOME, javaHome));

            initLogEventSourcesConfigProp(configDir, pluginConfiguration);

            DiscoveredResourceDetails resourceDetails = createResourceDetails(discoveryContext, pluginConfiguration,
                    processInfo, installInfo);
            resources.add(resourceDetails);
        }
        return resources;
    }

    @Nullable
    private DiscoveredResourceDetails discoverInProcessJBossAS(ResourceDiscoveryContext discoveryContext) {
        try {
            return new InProcessJBossASDiscovery().discoverInProcessJBossAS(discoveryContext);
        } catch (Throwable t) {
            log.debug(
                    "In-process JBoss AS discovery failed - we are probably not running embedded within JBoss AS.",
                    t);
            return null;
        }
    }

    private DiscoveredResourceDetails createResourceDetails(ResourceDiscoveryContext discoveryContext,
            Configuration pluginConfig, @Nullable ProcessInfo processInfo, JBossInstallationInfo installInfo) {
        String serverHomeDir = pluginConfig
                .getSimple(ApplicationServerPluginConfigurationProperties.SERVER_HOME_DIR).getStringValue();
        File absoluteConfigPath = resolvePathRelativeToHomeDir(pluginConfig, serverHomeDir);

        // Canonicalize the config path, so it's consistent no matter how it's entered.
        // This prevents two servers with different forms of the same config path, but
        // that are actually the same server, from ending up in inventory.
        // JON: fix for JBNADM-2634 - do not resolve symlinks (ips, 12/18/07)
        String key = FileUtils.getCanonicalPath(absoluteConfigPath.getPath());

        String bindAddress = pluginConfig.getSimple(ApplicationServerPluginConfigurationProperties.BIND_ADDRESS)
                .getStringValue();
        String namingUrl = pluginConfig.getSimple(ApplicationServerPluginConfigurationProperties.NAMING_URL)
                .getStringValue();

        // If we were able to discover the JNP URL, include the JNP port in the Resource name.
        String namingPort = null;
        //noinspection ConstantConditions
        if (namingUrl != null) {
            URI uri = URI.create(namingUrl);
            if (uri.getPort() != -1) {
                namingPort = String.valueOf(uri.getPort());
            }
        }

        final JBossProductType productType = installInfo.getProductType();
        String description = productType.DESCRIPTION + " " + installInfo.getMajorVersion();
        File deployDir = new File(absoluteConfigPath, "deploy");

        File rhqInstallerWar = new File(deployDir, "rhq-installer.war");
        File rhqInstallerWarUndeployed = new File(deployDir, "rhq-installer.war.rej");
        boolean isRhqServer = rhqInstallerWar.exists() || rhqInstallerWarUndeployed.exists();
        if (isRhqServer) {
            description += " hosting the RHQ Server";
            // We know this is an RHQ Server. Let's add an event source for its server log file, but disable it by default.
            configureEventSourceForServerLogFile(pluginConfig);
        }
        final String PRODUCT_PREFIX = productType.name() + " ";
        String name = PRODUCT_PREFIX + formatServerName(bindAddress, namingPort,
                discoveryContext.getSystemInformation().getHostname(), absoluteConfigPath.getName(), isRhqServer);

        return new DiscoveredResourceDetails(discoveryContext.getResourceType(), key, name,
                PRODUCT_PREFIX + installInfo.getVersion(), description, pluginConfig, processInfo);
    }

    public String formatServerName(String bindingAddress, String jnpPort, String hostname, String configurationName,
            boolean isRhq) {

        if (isRhq) {
            return hostname + " RHQ Server";
        } else {
            String hostnameToUse = hostname;

            if (bindingAddress != null) {
                try {
                    InetAddress bindAddr = InetAddress.getByName(bindingAddress);
                    if (!bindAddr.isAnyLocalAddress()) {
                        //if the binding address != 0.0.0.0
                        hostnameToUse = bindAddr.getHostName();
                    }
                } catch (UnknownHostException e) {
                    //this should not happen?
                    log.warn("Unknown hostname passed in as the binding address for JBoss AS server discovery: "
                            + bindingAddress);
                }
            }

            if (jnpPort != null) {
                hostnameToUse += ":" + jnpPort;
            }

            return hostnameToUse + " " + configurationName;
        }
    }

    private void configureEventSourceForServerLogFile(Configuration pluginConfig) {
        File rhqLogFile = resolvePathRelativeToHomeDir(pluginConfig, "../logs/rhq-server-log4j.log");
        if (rhqLogFile.exists() && !rhqLogFile.isDirectory()) {
            try {
                PropertyMap serverLogEventSource = new PropertyMap("logEventSource");
                serverLogEventSource.put(new PropertySimple(
                        LogFileEventResourceComponentHelper.LogEventSourcePropertyNames.LOG_FILE_PATH,
                        rhqLogFile.getCanonicalPath()));
                serverLogEventSource.put(new PropertySimple(
                        LogFileEventResourceComponentHelper.LogEventSourcePropertyNames.ENABLED, Boolean.FALSE));
                serverLogEventSource.put(new PropertySimple(
                        LogFileEventResourceComponentHelper.LogEventSourcePropertyNames.MINIMUM_SEVERITY, "info"));
                PropertyList logEventSources = pluginConfig
                        .getList(LogFileEventResourceComponentHelper.LOG_EVENT_SOURCES_CONFIG_PROP);
                logEventSources.add(serverLogEventSource);
            } catch (IOException e) {
                log.warn("Unable to setup RHQ Server log file monitoring.", e);
            }
        }
    }

    private String getJnpURL(JBossInstanceInfo cmdLine, File installHome, File configDir) {
        File jnpServiceUrlFile = new File(configDir, "data/jnp-service.url");
        if (jnpServiceUrlFile.exists() && jnpServiceUrlFile.canRead()) {
            BufferedReader br = null;
            try {
                br = new BufferedReader(new FileReader(jnpServiceUrlFile));
                String jnpUrl = br.readLine();
                if (jnpUrl != null) {
                    if (log.isDebugEnabled()) {
                        log.debug("Read JNP URL from jnp-service.url file: " + jnpUrl);
                    }
                    return jnpUrl;
                }
            } catch (IOException ioe) {
                // Nothing to do
            } finally {
                if (br != null) {
                    try {
                        br.close();
                    } catch (IOException e) {
                        // nada
                    }
                }
            }
        }

        log.warn("Failed to read JNP URL from '" + jnpServiceUrlFile + "'.");

        // Above did not work, so fall back to our previous scheme
        JnpConfig jnpConfig = getJnpConfig(installHome, configDir, cmdLine.getSystemProperties());

        String jnpAddress = (jnpConfig.getJnpAddress() != null) ? jnpConfig.getJnpAddress() : null;
        Integer jnpPort = (jnpConfig.getJnpPort() != null) ? jnpConfig.getJnpPort() : null;

        if (jnpAddress == null || jnpPort == null) {
            log.warn("Failed to discover JNP URL for JBoss instance with configuration directory [" + configDir
                    + "].");
            return null;
        }

        if (ANY_ADDRESS.equals(jnpAddress)) {
            jnpAddress = LOCALHOST;
        }

        return "jnp://" + jnpAddress + ":" + jnpPort;
    }

    private static JnpConfig getJnpConfig(File installHome, File configDir, Properties props) {
        File serviceXML = new File(configDir, JBOSS_SERVICE_XML);
        JnpConfig config = JnpConfig.getConfig(serviceXML, props);
        if ((config == null) || (config.getJnpPort() == null)) {
            File namingServiceFile = new File(configDir, JBOSS_NAMING_SERVICE_XML);
            if (namingServiceFile.exists()) {
                config = JnpConfig.getConfig(namingServiceFile, props);
            }
        }
        return config;
    }

    private void initLogEventSourcesConfigProp(File configDir, Configuration pluginConfig) {
        File logDir = new File(configDir, "log");
        File serverLogFile = new File(logDir, "server.log");
        if (serverLogFile.exists() && !serverLogFile.isDirectory()) {
            PropertyMap serverLogEventSource = new PropertyMap("logEventSource");
            serverLogEventSource.put(new PropertySimple(
                    LogFileEventResourceComponentHelper.LogEventSourcePropertyNames.LOG_FILE_PATH, serverLogFile));
            serverLogEventSource.put(new PropertySimple(
                    LogFileEventResourceComponentHelper.LogEventSourcePropertyNames.ENABLED, Boolean.FALSE));
            PropertyList logEventSources = pluginConfig
                    .getList(LogFileEventResourceComponentHelper.LOG_EVENT_SOURCES_CONFIG_PROP);
            logEventSources.add(serverLogEventSource);
        }
    }

    private boolean isSupportedProduct(JBossInstallationInfo installInfo) {
        ComparableVersion version = new ComparableVersion(installInfo.getVersion());
        JBossProductType productType = installInfo.getProductType();
        ComparableVersion minimumVersion = MINIMUM_PRODUCT_VERSIONS.get(productType);
        // The product is supported if the version is greater than or equal to the minimum version.
        boolean supported = (version.compareTo(minimumVersion) >= 0);
        if (!supported) {
            log.debug(productType + " version " + version + " is not supported by this plugin (minimum "
                    + productType + " version is " + minimumVersion + ") - skipping...");
        }
        return supported;
    }

    @NotNull
    private static File resolvePathRelativeToHomeDir(Configuration pluginConfig, @NotNull String path) {
        File configDir = new File(path);
        if (!configDir.isAbsolute()) {
            String homeDir = pluginConfig.getSimple(ApplicationServerPluginConfigurationProperties.HOME_DIR)
                    .getStringValue();
            configDir = new File(homeDir, path);
        }
        return configDir;
    }
}