From 001d5b1eeb39b35e4b79e49b50af22bb8ea30b0e Mon Sep 17 00:00:00 2001 From: Doug Hilpipre Date: Wed, 15 Sep 2021 09:22:51 -0500 Subject: [PATCH] adds support for java.lang.Date and fixes problem related java.sql.Time --- .../java/org/newrelic/nrjmx/Application.java | 114 +-- .../java/org/newrelic/nrjmx/Arguments.java | 349 +++++----- .../java/org/newrelic/nrjmx/JMXFetcher.java | 650 ++++++++++-------- src/main/java/org/newrelic/nrjmx/Logging.java | 37 +- 4 files changed, 636 insertions(+), 514 deletions(-) diff --git a/src/main/java/org/newrelic/nrjmx/Application.java b/src/main/java/org/newrelic/nrjmx/Application.java index 71423bee..b1e92b82 100644 --- a/src/main/java/org/newrelic/nrjmx/Application.java +++ b/src/main/java/org/newrelic/nrjmx/Application.java @@ -1,69 +1,77 @@ /* -* Copyright 2020 New Relic Corporation. All rights reserved. -* SPDX-License-Identifier: Apache-2.0 -*/ + * Copyright 2020 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ package org.newrelic.nrjmx; -import org.apache.commons.cli.HelpFormatter; - import java.util.logging.Logger; +import org.apache.commons.cli.HelpFormatter; public class Application { - public static void printHelp() { - new HelpFormatter().printHelp("nrjmx", Arguments.options()); - } - public static void main(String[] args) { - Arguments cliArgs = null; - try { - cliArgs = Arguments.from(args); - } catch (Exception e) { - System.err.println(e.getMessage()); - printHelp(); - System.exit(1); - } + public static void printHelp() { + new HelpFormatter().printHelp("nrjmx", Arguments.options()); + } - if (cliArgs.isHelp()) { - printHelp(); - System.exit(0); - } + public static void main(String[] args) { + Arguments cliArgs = null; + try { + cliArgs = Arguments.from(args); + } catch (Exception e) { + System.err.println(e.getMessage()); + printHelp(); + System.exit(1); + } + + if (cliArgs.isHelp()) { + printHelp(); + System.exit(0); + } - Logger logger = Logger.getLogger("nrjmx"); - Logging.setup(logger, cliArgs.isVerbose()); + Logger logger = Logger.getLogger("nrjmx"); + Logging.setup(logger, cliArgs.isVerbose()); - // Instantiate a JMXFetcher from the configuration arguments - JMXFetcher fetcher = cliArgs.getConnectionURL().equals("") ? - new JMXFetcher( - cliArgs.getHostname(), cliArgs.getPort(), cliArgs.getUriPath(), - cliArgs.getUsername(), cliArgs.getPassword(), - cliArgs.getKeyStore(), cliArgs.getKeyStorePassword(), - cliArgs.getTrustStore(), cliArgs.getTrustStorePassword(), - cliArgs.getIsRemoteJMX(), cliArgs.getIsRemoteJBossStandalone() - ) : - new JMXFetcher( - cliArgs.getConnectionURL(), - cliArgs.getUsername(), cliArgs.getPassword(), - cliArgs.getKeyStore(), cliArgs.getKeyStorePassword(), - cliArgs.getTrustStore(), cliArgs.getTrustStorePassword() - ); + // Instantiate a JMXFetcher from the configuration arguments + JMXFetcher fetcher = + cliArgs.getConnectionURL().equals("") + ? new JMXFetcher( + cliArgs.getHostname(), + cliArgs.getPort(), + cliArgs.getUriPath(), + cliArgs.getUsername(), + cliArgs.getPassword(), + cliArgs.getKeyStore(), + cliArgs.getKeyStorePassword(), + cliArgs.getTrustStore(), + cliArgs.getTrustStorePassword(), + cliArgs.getIsRemoteJMX(), + cliArgs.getIsRemoteJBossStandalone()) + : new JMXFetcher( + cliArgs.getConnectionURL(), + cliArgs.getUsername(), + cliArgs.getPassword(), + cliArgs.getKeyStore(), + cliArgs.getKeyStorePassword(), + cliArgs.getTrustStore(), + cliArgs.getTrustStorePassword()); - try { - fetcher.run(System.in, System.out); - } catch (JMXFetcher.ConnectionError e) { - logger.severe("jmx connection error: " + e.getMessage()); - logTrace(cliArgs, logger, e); - System.exit(1); - } catch (Exception e) { - logger.severe("error running nrjmx: " + e.getMessage()); - logTrace(cliArgs, logger, e); - System.exit(1); - } + try { + fetcher.run(System.in, System.out); + } catch (JMXFetcher.ConnectionError e) { + logger.severe("jmx connection error: " + e.getMessage()); + logTrace(cliArgs, logger, e); + System.exit(1); + } catch (Exception e) { + logger.severe("error running nrjmx: " + e.getMessage()); + logTrace(cliArgs, logger, e); + System.exit(1); } + } - private static void logTrace(Arguments cliArgs, Logger logger, Exception e) { - if (cliArgs.isDebugMode()) { - logger.info("exception trace for " + e.getClass().getCanonicalName() + ": " + e); - } + private static void logTrace(Arguments cliArgs, Logger logger, Exception e) { + if (cliArgs.isDebugMode()) { + logger.info("exception trace for " + e.getClass().getCanonicalName() + ": " + e); } + } } diff --git a/src/main/java/org/newrelic/nrjmx/Arguments.java b/src/main/java/org/newrelic/nrjmx/Arguments.java index 15b8121d..fdbadb9c 100644 --- a/src/main/java/org/newrelic/nrjmx/Arguments.java +++ b/src/main/java/org/newrelic/nrjmx/Arguments.java @@ -1,169 +1,196 @@ /* -* Copyright 2020 New Relic Corporation. All rights reserved. -* SPDX-License-Identifier: Apache-2.0 -*/ + * Copyright 2020 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ package org.newrelic.nrjmx; -import org.apache.commons.cli.*; +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.DefaultParser; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; class Arguments { - private String hostname; - private String connectionURL; - private int port; - private String uriPath; - private String username; - private String password; - private String keyStore; - private String keyStorePassword; - private String trustStore; - private String trustStorePassword; - private boolean verbose; - private boolean debug; - private boolean isRemoteJMX; - private boolean isRemoteJBossStandalone; - private boolean help; - - private static Options options = null; - - private Arguments() { - } - - static Options options() { - if (options == null) { - options = new Options(); - Option connectionURL = Option.builder("C") - .longOpt("connURL").desc("full connection URL. Default none.").hasArg().build(); - options.addOption(connectionURL); - Option hostname = Option.builder("H") - .longOpt("hostname").desc("JMX hostname (localhost)").hasArg().build(); - options.addOption(hostname); - Option port = Option.builder("P") - .longOpt("port").desc("JMX port (7199)").hasArg().build(); - options.addOption(port); - Option uriPath = Option.builder("U") - .longOpt("uriPath").desc("path for the JMX service URI. Defaults to jmxrmi").hasArg().build(); - options.addOption(uriPath); - Option username = Option.builder("u") - .longOpt("username").desc("JMX username").hasArg().build(); - options.addOption(username); - Option password = Option.builder("p") - .longOpt("password").desc("JMX password").hasArg().build(); - options.addOption(password); - - - Option keyStore = Option.builder("keyStore") - .longOpt("keyStore").desc("SSL keyStore location").hasArg().build(); - options.addOption(keyStore); - Option keyStorePassword = Option.builder("keyStorePassword") - .longOpt("keyStorePassword").desc("SSL keyStorePassword").hasArg().build(); - options.addOption(keyStorePassword); - Option trustStore = Option.builder("trustStore") - .longOpt("trustStore").desc("SSL trustStore location").hasArg().build(); - options.addOption(trustStore); - Option trustStorePassword = Option.builder("trustStorePassword") - .longOpt("trustStorePassword").desc("SSL trustStorePassword").hasArg().build(); - options.addOption(trustStorePassword); - - Option verbose = Option.builder("v") - .longOpt("verbose").desc("Verbose output").hasArg(false).build(); - options.addOption(verbose); - Option debug = Option.builder("d") - .longOpt("debug").desc("Debug mode").hasArg(false).build(); - options.addOption(debug); - Option help = Option.builder("h") - .longOpt("help").desc("Show help").hasArg(false).build(); - options.addOption(help); - - Option remote = Option.builder("r") - .longOpt("remote").desc("Remote JMX mode").hasArg(false).build(); - options.addOption(remote); - Option remoteJBossStandalone = Option.builder("s") - .longOpt("remoteJBossStandalone").desc("Remote JBoss Standalone mode").hasArg(false).build(); - options.addOption(remoteJBossStandalone); - } - return options; - } - - static Arguments from(String[] args) throws ParseException { - CommandLine cmd = new DefaultParser().parse(options(), args); - - Arguments argsObj = new Arguments(); - argsObj.connectionURL = cmd.getOptionValue("connURL", ""); - argsObj.hostname = cmd.getOptionValue("hostname", "localhost"); - argsObj.port = Integer.parseInt(cmd.getOptionValue("port", "7199")); - argsObj.uriPath = cmd.getOptionValue("uriPath", "jmxrmi"); - argsObj.username = cmd.getOptionValue("username", ""); - argsObj.password = cmd.getOptionValue("password", ""); - argsObj.keyStore = cmd.getOptionValue("keyStore", ""); - argsObj.keyStorePassword = cmd.getOptionValue("keyStorePassword", ""); - argsObj.trustStore = cmd.getOptionValue("trustStore", ""); - argsObj.trustStorePassword = cmd.getOptionValue("trustStorePassword", ""); - argsObj.verbose = cmd.hasOption("verbose"); - argsObj.help = cmd.hasOption("help"); - argsObj.debug = cmd.hasOption("debug"); - argsObj.isRemoteJMX = cmd.hasOption("remote"); - argsObj.isRemoteJBossStandalone = cmd.hasOption("remoteJBossStandalone"); - return argsObj; - } - - String getConnectionURL() { - return connectionURL; - } - - String getHostname() { - return hostname; - } - - int getPort() { - return port; - } - - String getUriPath() { - return uriPath; - } - - String getUsername() { - return username; - } - - boolean getIsRemoteJMX() { - return isRemoteJMX; - } - boolean getIsRemoteJBossStandalone() { - return isRemoteJBossStandalone; - } - - String getPassword() { - return password; - } - - String getKeyStore() { - return keyStore; - } - - String getKeyStorePassword() { - return keyStorePassword; - } - - String getTrustStore() { - return trustStore; - } - - String getTrustStorePassword() { - return trustStorePassword; - } - - boolean isVerbose() { - return verbose; - } - - boolean isDebugMode() { - return debug; - } - - boolean isHelp() { - return help; - } + private static Options options = null; + private String hostname; + private String connectionURL; + private int port; + private String uriPath; + private String username; + private String password; + private String keyStore; + private String keyStorePassword; + private String trustStore; + private String trustStorePassword; + private boolean verbose; + private boolean debug; + private boolean isRemoteJMX; + private boolean isRemoteJBossStandalone; + private boolean help; + + private Arguments() {} + + static Options options() { + if (options == null) { + options = new Options(); + Option connectionURL = + Option.builder("C") + .longOpt("connURL") + .desc("full connection URL. Default none.") + .hasArg() + .build(); + options.addOption(connectionURL); + Option hostname = + Option.builder("H").longOpt("hostname").desc("JMX hostname (localhost)").hasArg().build(); + options.addOption(hostname); + Option port = Option.builder("P").longOpt("port").desc("JMX port (7199)").hasArg().build(); + options.addOption(port); + Option uriPath = + Option.builder("U") + .longOpt("uriPath") + .desc("path for the JMX service URI. Defaults to jmxrmi") + .hasArg() + .build(); + options.addOption(uriPath); + Option username = + Option.builder("u").longOpt("username").desc("JMX username").hasArg().build(); + options.addOption(username); + Option password = + Option.builder("p").longOpt("password").desc("JMX password").hasArg().build(); + options.addOption(password); + + Option keyStore = + Option.builder("keyStore") + .longOpt("keyStore") + .desc("SSL keyStore location") + .hasArg() + .build(); + options.addOption(keyStore); + Option keyStorePassword = + Option.builder("keyStorePassword") + .longOpt("keyStorePassword") + .desc("SSL keyStorePassword") + .hasArg() + .build(); + options.addOption(keyStorePassword); + Option trustStore = + Option.builder("trustStore") + .longOpt("trustStore") + .desc("SSL trustStore location") + .hasArg() + .build(); + options.addOption(trustStore); + Option trustStorePassword = + Option.builder("trustStorePassword") + .longOpt("trustStorePassword") + .desc("SSL trustStorePassword") + .hasArg() + .build(); + options.addOption(trustStorePassword); + + Option verbose = + Option.builder("v").longOpt("verbose").desc("Verbose output").hasArg(false).build(); + options.addOption(verbose); + Option debug = Option.builder("d").longOpt("debug").desc("Debug mode").hasArg(false).build(); + options.addOption(debug); + Option help = Option.builder("h").longOpt("help").desc("Show help").hasArg(false).build(); + options.addOption(help); + + Option remote = + Option.builder("r").longOpt("remote").desc("Remote JMX mode").hasArg(false).build(); + options.addOption(remote); + Option remoteJBossStandalone = + Option.builder("s") + .longOpt("remoteJBossStandalone") + .desc("Remote JBoss Standalone mode") + .hasArg(false) + .build(); + options.addOption(remoteJBossStandalone); + } + return options; + } + + static Arguments from(String[] args) throws ParseException { + CommandLine cmd = new DefaultParser().parse(options(), args); + + Arguments argsObj = new Arguments(); + argsObj.connectionURL = cmd.getOptionValue("connURL", ""); + argsObj.hostname = cmd.getOptionValue("hostname", "localhost"); + argsObj.port = Integer.parseInt(cmd.getOptionValue("port", "7199")); + argsObj.uriPath = cmd.getOptionValue("uriPath", "jmxrmi"); + argsObj.username = cmd.getOptionValue("username", ""); + argsObj.password = cmd.getOptionValue("password", ""); + argsObj.keyStore = cmd.getOptionValue("keyStore", ""); + argsObj.keyStorePassword = cmd.getOptionValue("keyStorePassword", ""); + argsObj.trustStore = cmd.getOptionValue("trustStore", ""); + argsObj.trustStorePassword = cmd.getOptionValue("trustStorePassword", ""); + argsObj.verbose = cmd.hasOption("verbose"); + argsObj.help = cmd.hasOption("help"); + argsObj.debug = cmd.hasOption("debug"); + argsObj.isRemoteJMX = cmd.hasOption("remote"); + argsObj.isRemoteJBossStandalone = cmd.hasOption("remoteJBossStandalone"); + return argsObj; + } + + String getConnectionURL() { + return connectionURL; + } + + String getHostname() { + return hostname; + } + + int getPort() { + return port; + } + + String getUriPath() { + return uriPath; + } + + String getUsername() { + return username; + } + + boolean getIsRemoteJMX() { + return isRemoteJMX; + } + + boolean getIsRemoteJBossStandalone() { + return isRemoteJBossStandalone; + } + + String getPassword() { + return password; + } + + String getKeyStore() { + return keyStore; + } + + String getKeyStorePassword() { + return keyStorePassword; + } + + String getTrustStore() { + return trustStore; + } + + String getTrustStorePassword() { + return trustStorePassword; + } + + boolean isVerbose() { + return verbose; + } + + boolean isDebugMode() { + return debug; + } + + boolean isHelp() { + return help; + } } diff --git a/src/main/java/org/newrelic/nrjmx/JMXFetcher.java b/src/main/java/org/newrelic/nrjmx/JMXFetcher.java index b3052a2a..41c4c92d 100644 --- a/src/main/java/org/newrelic/nrjmx/JMXFetcher.java +++ b/src/main/java/org/newrelic/nrjmx/JMXFetcher.java @@ -1,26 +1,23 @@ /* -* Copyright 2020 New Relic Corporation. All rights reserved. -* SPDX-License-Identifier: Apache-2.0 -*/ + * Copyright 2020 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ package org.newrelic.nrjmx; import com.google.gson.Gson; - import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintStream; - import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.Properties; +import java.util.Scanner; import java.util.Set; -import java.util.logging.Logger; import java.util.logging.Level; -import java.util.Scanner; - +import java.util.logging.Logger; import javax.management.Attribute; import javax.management.InstanceNotFoundException; import javax.management.IntrospectionException; @@ -35,325 +32,410 @@ import javax.management.remote.JMXConnector; import javax.management.remote.JMXConnectorFactory; import javax.management.remote.JMXServiceURL; - import javax.rmi.ssl.SslRMIClientSocketFactory; - /** - * JMXFetcher class reads queries from an InputStream (usually stdin) and sends the results to an OutputStream - * (usually stdout) + * JMXFetcher class reads queries from an InputStream (usually stdin) and sends the results to an + * OutputStream (usually stdout) */ public class JMXFetcher { - public static final String defaultURIPath = "jmxrmi"; - public static final Boolean defaultJBossModeIsStandalone = false; - - private static final Logger logger = Logger.getLogger("nrjmx"); - - private MBeanServerConnection connection; - private Map result = new HashMap<>(); - private String connectionString; - private Map connectionEnv = new HashMap<>(); - public class ConnectionError extends Exception { - public ConnectionError(String message, Exception cause) { - super(message, cause); + public static final String defaultURIPath = "jmxrmi"; + public static final Boolean defaultJBossModeIsStandalone = false; + + private static final Logger logger = Logger.getLogger("nrjmx"); + + private MBeanServerConnection connection; + private Map result = new HashMap<>(); + private String connectionString; + private Map connectionEnv = new HashMap<>(); + + /** + * Builds a new JMXFetcher with user & pass from a connection URL. + * + * @param connectionURL Full connection URL. + * @param username User name of the JMX endpoint, or an empty string if authentication is disabled + * @param password Password of the JMX endpoint, or an empty string if authentication is disabled + * @param keyStore Path of the client keystore file + * @param keyStorePassword Password of the keystore file + * @param trustStore Path of the client trust store file + * @param trustStorePassword Password of the trust store file + */ + public JMXFetcher( + String connectionURL, + String username, + String password, + String keyStore, + String keyStorePassword, + String trustStore, + String trustStorePassword) { + this( + "", + 0, + "", + username, + password, + keyStore, + keyStorePassword, + trustStore, + trustStorePassword, + false, + false, + connectionURL); + } + + /** + * Builds a new JMXFetcher building the connection URL from arguments. + * + * @param hostname Hostname of the JMX endpoint + * @param port Port of the JMX endpoint + * @param username User name of the JMX endpoint, or an empty string if authentication is disabled + * @param password Password of the JMX endpoint, or an empty string if authentication is disabled + * @param keyStore Path of the client keystore file + * @param keyStorePassword Password of the keystore file + * @param trustStore Path of the client trust store file + * @param trustStorePassword Password of the trust store file + * @param isRemote true if the connection is remote. False otherwise. + */ + public JMXFetcher( + String hostname, + int port, + String username, + String password, + String keyStore, + String keyStorePassword, + String trustStore, + String trustStorePassword, + boolean isRemote) { + this( + hostname, + port, + defaultURIPath, + username, + password, + keyStore, + keyStorePassword, + trustStore, + trustStorePassword, + isRemote, + defaultJBossModeIsStandalone); + } + + /** + * Builds a new JMXFetcher building the connection URL from arguments, with custom URI. + * + * @param hostname Hostname of the JMX endpoint + * @param port Port of the JMX endpoint + * @param uriPath URI path for the JMX endpoint + * @param username User name of the JMX endpoint, or an empty string if authentication is disabled + * @param password Password of the JMX endpoint, or an empty string if authentication is disabled + * @param keyStore Path of the client keystore file + * @param keyStorePassword Password of the keystore file + * @param trustStore Path of the client trust store file + * @param trustStorePassword Password of the trust store file + * @param isRemote true if the connection is remote. False otherwise. + * @param isJBossStandaloneMode false if JBoss is running on Domain-mode, true for JBoss + * Standalone mode. + */ + public JMXFetcher( + String hostname, + int port, + String uriPath, + String username, + String password, + String keyStore, + String keyStorePassword, + String trustStore, + String trustStorePassword, + boolean isRemote, + boolean isJBossStandaloneMode) { + this( + hostname, + port, + uriPath, + username, + password, + keyStore, + keyStorePassword, + trustStore, + trustStorePassword, + isRemote, + isJBossStandaloneMode, + ""); + } + + private JMXFetcher( + String hostname, + int port, + String uriPath, + String username, + String password, + String keyStore, + String keyStorePassword, + String trustStore, + String trustStorePassword, + boolean isRemote, + boolean isJBossStandaloneMode, + String connectionURL) { + if (connectionURL != null && !connectionURL.equals("")) { + connectionString = connectionURL; + } else { + // Official doc for remoting v3 is not available, see: + // - https://developer.jboss.org/thread/196619 + // - http://jbossremoting.jboss.org/documentation/v3.html + // Some doc on URIS at: + // - + // https://github.com/jboss-remoting/jboss-remoting/blob/master/src/main/java/org/jboss/remoting3/EndpointImpl.java#L292-L304 + // - https://stackoverflow.com/questions/42970921/what-is-http-remoting-protocol + // - + // http://www.mastertheboss.com/jboss-server/jboss-monitoring/using-jconsole-to-monitor-a-remote-wildfly-server + if (isRemote) { + if (defaultURIPath.equals(uriPath)) { + uriPath = ""; + } else { + uriPath = uriPath.concat("/"); } - } - - public class QueryError extends Exception { - public QueryError(String message, Exception cause) { - super(message, cause); + String remoteProtocol = "remote"; + if (isJBossStandaloneMode) { + remoteProtocol = "remote+http"; } + connectionString = + String.format("service:jmx:%s://%s:%s%s", remoteProtocol, hostname, port, uriPath); + } else { + connectionString = + String.format("service:jmx:rmi:///jndi/rmi://%s:%s/%s", hostname, port, uriPath); + } } - public class ValueError extends Exception { - public ValueError(String message) { - super(message); - } + if (!"".equals(username)) { + connectionEnv.put(JMXConnector.CREDENTIALS, new String[] {username, password}); } - /** - * Builds a new JMXFetcher with user & pass from a connection URL. - * - * @param connectionURL Full connection URL. - * @param username User name of the JMX endpoint, or an empty string if authentication is disabled - * @param password Password of the JMX endpoint, or an empty string if authentication is disabled - * @param keyStore Path of the client keystore file - * @param keyStorePassword Password of the keystore file - * @param trustStore Path of the client trust store file - * @param trustStorePassword Password of the trust store file - */ - public JMXFetcher(String connectionURL, String username, String password, String keyStore, - String keyStorePassword, String trustStore, String trustStorePassword) { - this("", 0, "", username, password, keyStore, - keyStorePassword, trustStore, trustStorePassword, false, false, connectionURL); + if (!"".equals(keyStore) && !"".equals(trustStore)) { + Properties p = System.getProperties(); + p.put("javax.net.ssl.keyStore", keyStore); + p.put("javax.net.ssl.keyStorePassword", keyStorePassword); + p.put("javax.net.ssl.trustStore", trustStore); + p.put("javax.net.ssl.trustStorePassword", trustStorePassword); + connectionEnv.put("com.sun.jndi.rmi.factory.socket", new SslRMIClientSocketFactory()); } - - /** - * Builds a new JMXFetcher building the connection URL from arguments. - * - * @param hostname Hostname of the JMX endpoint - * @param port Port of the JMX endpoint - * @param username User name of the JMX endpoint, or an empty string if authentication is disabled - * @param password Password of the JMX endpoint, or an empty string if authentication is disabled - * @param keyStore Path of the client keystore file - * @param keyStorePassword Password of the keystore file - * @param trustStore Path of the client trust store file - * @param trustStorePassword Password of the trust store file - * @param isRemote true if the connection is remote. False otherwise. - */ - public JMXFetcher(String hostname, int port, String username, String password, String keyStore, - String keyStorePassword, String trustStore, String trustStorePassword, boolean isRemote) { - this(hostname, port, defaultURIPath, username, password, keyStore, - keyStorePassword, trustStore, trustStorePassword, isRemote, defaultJBossModeIsStandalone); + } + + /** + * Sends to JMX the queries from the InputStream and sends the JMX results to an OutputStream. + * Each query is read from a single line and the respective result is sent as a line to the + * outputstream. + * + *

If the query is wrong, it just ignores it and does not sends any data to the output stream. + * + * @param inputStream Source of the JMX queries. + * @param outputStream Destination of the found JMX MBeans. + * @throws ConnectionError If the connection to the JMX server has failed. + */ + public void run(InputStream inputStream, OutputStream outputStream) throws ConnectionError { + try { + JMXServiceURL address = new JMXServiceURL(connectionString); + JMXConnector connector = JMXConnectorFactory.connect(address, connectionEnv); + connection = connector.getMBeanServerConnection(); + } catch (IOException e) { + throw new ConnectionError("Can't connect to JMX server: " + connectionString, e); } - /** - * Builds a new JMXFetcher building the connection URL from arguments, with custom URI. - * - * @param hostname Hostname of the JMX endpoint - * @param port Port of the JMX endpoint - * @param uriPath URI path for the JMX endpoint - * @param username User name of the JMX endpoint, or an empty string if authentication is disabled - * @param password Password of the JMX endpoint, or an empty string if authentication is disabled - * @param keyStore Path of the client keystore file - * @param keyStorePassword Password of the keystore file - * @param trustStore Path of the client trust store file - * @param trustStorePassword Password of the trust store file - * @param isRemote true if the connection is remote. False otherwise. - * @param isJBossStandaloneMode false if JBoss is running on Domain-mode, true for JBoss Standalone mode. - */ - public JMXFetcher(String hostname, int port, String uriPath, String username, String password, String keyStore, - String keyStorePassword, String trustStore, String trustStorePassword, boolean isRemote, - boolean isJBossStandaloneMode) { - this(hostname, port, uriPath, username, password, keyStore, - keyStorePassword, trustStore, trustStorePassword, isRemote, isJBossStandaloneMode, ""); - } + Gson gson = new Gson(); + try (Scanner input = new Scanner(inputStream); + PrintStream output = new PrintStream(outputStream)) { + while (input.hasNextLine()) { + String beanName = input.nextLine(); - private JMXFetcher(String hostname, int port, String uriPath, String username, String password, String keyStore, - String keyStorePassword, String trustStore, String trustStorePassword, boolean isRemote, - boolean isJBossStandaloneMode, String connectionURL) { - if (connectionURL != null && !connectionURL.equals("")) { - connectionString = connectionURL; - } else { - // Official doc for remoting v3 is not available, see: - // - https://developer.jboss.org/thread/196619 - // - http://jbossremoting.jboss.org/documentation/v3.html - // Some doc on URIS at: - // - https://github.com/jboss-remoting/jboss-remoting/blob/master/src/main/java/org/jboss/remoting3/EndpointImpl.java#L292-L304 - // - https://stackoverflow.com/questions/42970921/what-is-http-remoting-protocol - // - http://www.mastertheboss.com/jboss-server/jboss-monitoring/using-jconsole-to-monitor-a-remote-wildfly-server - if (isRemote) { - if (defaultURIPath.equals(uriPath)) { - uriPath = ""; - } else { - uriPath = uriPath.concat("/"); - } - String remoteProtocol = "remote"; - if (isJBossStandaloneMode) { - remoteProtocol = "remote+http"; - } - connectionString = String.format("service:jmx:%s://%s:%s%s", remoteProtocol, hostname, port, uriPath); - } else { - connectionString = String.format("service:jmx:rmi:///jndi/rmi://%s:%s/%s", hostname, port, uriPath); - } + Set beanInstances; + try { + beanInstances = query(beanName); + } catch (JMXFetcher.QueryError e) { + logger.warning(e.getMessage()); + logger.log(Level.FINE, e.getMessage(), e); + continue; } - if (!"".equals(username)) { - connectionEnv.put(JMXConnector.CREDENTIALS, new String[]{username, password}); + for (ObjectInstance instance : beanInstances) { + try { + queryAttributes(instance); + } catch (JMXFetcher.QueryError e) { + logger.warning(e.getMessage()); + logger.log(Level.FINE, e.getMessage(), e); + } } - - if (!"".equals(keyStore) && !"".equals(trustStore)) { - Properties p = System.getProperties(); - p.put("javax.net.ssl.keyStore", keyStore); - p.put("javax.net.ssl.keyStorePassword", keyStorePassword); - p.put("javax.net.ssl.trustStore", trustStore); - p.put("javax.net.ssl.trustStorePassword", trustStorePassword); - connectionEnv.put("com.sun.jndi.rmi.factory.socket", new SslRMIClientSocketFactory()); + try { + output.println(gson.toJson(popResults())); + } catch (IllegalArgumentException e) { + logger.warning(e.getMessage()); + logger.log(Level.FINE, e.getMessage(), e); } + } + logger.fine("Stopped receiving data, leaving...\n"); } + } - /** - * Sends to JMX the queries from the InputStream and sends the JMX results to an OutputStream. Each query is - * read from a single line and the respective result is sent as a line to the outputstream. - *

- * If the query is wrong, it just ignores it and does not sends any data to the output stream. - * - * @param inputStream Source of the JMX queries. - * @param outputStream Destination of the found JMX MBeans. - * @throws ConnectionError If the connection to the JMX server has failed. - */ - public void run(InputStream inputStream, OutputStream outputStream) throws ConnectionError { - try { - JMXServiceURL address = new JMXServiceURL(connectionString); - JMXConnector connector = JMXConnectorFactory.connect(address, connectionEnv); - connection = connector.getMBeanServerConnection(); - } catch (IOException e) { - throw new ConnectionError("Can't connect to JMX server: " + connectionString, e); - } + private Set query(String beanName) throws QueryError { + ObjectName queryObject; - Gson gson = new Gson(); - try (Scanner input = new Scanner(inputStream); - PrintStream output = new PrintStream(outputStream)) { - - while (input.hasNextLine()) { - String beanName = input.nextLine(); - - Set beanInstances; - try { - beanInstances = query(beanName); - } catch (JMXFetcher.QueryError e) { - logger.warning(e.getMessage()); - logger.log(Level.FINE, e.getMessage(), e); - continue; - } - - for (ObjectInstance instance : beanInstances) { - try { - queryAttributes(instance); - } catch (JMXFetcher.QueryError e) { - logger.warning(e.getMessage()); - logger.log(Level.FINE, e.getMessage(), e); - } - } - try { - output.println(gson.toJson(popResults())); - } catch (IllegalArgumentException e) { - logger.warning(e.getMessage()); - logger.log(Level.FINE, e.getMessage(), e); - } - } - logger.fine("Stopped receiving data, leaving...\n"); - } + try { + queryObject = new ObjectName(beanName); + } catch (MalformedObjectNameException e) { + throw new QueryError("Can't parse bean name " + beanName, e); } - private Set query(String beanName) throws QueryError { - ObjectName queryObject; + Set beanInstances; + try { + beanInstances = connection.queryMBeans(queryObject, null); + } catch (IOException e) { + throw new QueryError("Can't get beans for query " + beanName, e); + } - try { - queryObject = new ObjectName(beanName); - } catch (MalformedObjectNameException e) { - throw new QueryError("Can't parse bean name " + beanName, e); - } + return beanInstances; + } - Set beanInstances; - try { - beanInstances = connection.queryMBeans(queryObject, null); - } catch (IOException e) { - throw new QueryError("Can't get beans for query " + beanName, e); - } + private void queryAttributes(ObjectInstance instance) throws QueryError { + ObjectName objectName = instance.getObjectName(); + MBeanInfo info; - return beanInstances; + try { + info = connection.getMBeanInfo(objectName); + } catch (InstanceNotFoundException + | IntrospectionException + | ReflectionException + | IOException e) { + throw new QueryError("Can't find bean " + objectName.toString(), e); } - private void queryAttributes(ObjectInstance instance) throws QueryError { - ObjectName objectName = instance.getObjectName(); - MBeanInfo info; + MBeanAttributeInfo[] attrInfo = info.getAttributes(); - try { - info = connection.getMBeanInfo(objectName); - } catch (InstanceNotFoundException | IntrospectionException | ReflectionException | IOException e) { - throw new QueryError("Can't find bean " + objectName.toString(), e); - } + for (MBeanAttributeInfo attr : attrInfo) { + if (!attr.isReadable()) { + continue; + } - MBeanAttributeInfo[] attrInfo = info.getAttributes(); - - for (MBeanAttributeInfo attr : attrInfo) { - if (!attr.isReadable()) { - continue; - } - - String attrName = attr.getName(); - Object value; - - try { - value = connection.getAttribute(objectName, attrName); - if (value instanceof Attribute) { - Attribute jmxAttr = (Attribute) value; - value = jmxAttr.getValue(); - } - } catch (Exception e) { - logger.warning("Can't get attribute " + attrName + " for bean " + objectName.toString() + ": " + e.getMessage()); - continue; - } - - String name = String.format("%s,attr=%s", objectName.toString(), attrName); - try { - parseValue(name, value); - } catch (ValueError e) { - logger.fine(e.getMessage()); - } + String attrName = attr.getName(); + Object value; + + try { + value = connection.getAttribute(objectName, attrName); + if (value instanceof Attribute) { + Attribute jmxAttr = (Attribute) value; + value = jmxAttr.getValue(); } + } catch (Exception e) { + logger.warning( + "Can't get attribute " + + attrName + + " for bean " + + objectName.toString() + + ": " + + e.getMessage()); + continue; + } + + String name = String.format("%s,attr=%s", objectName.toString(), attrName); + try { + parseValue(name, value); + } catch (ValueError e) { + logger.fine(e.getMessage()); + } } + } + + private Map popResults() { + Map out = result; + result = new HashMap<>(); + return out; + } + + private void parseValue(String name, Object value) throws ValueError { + if (value == null) { + throw new ValueError("Found a null value for bean " + name); + } else if (value instanceof java.lang.Double) { + Double ddata = parseDouble((Double) value); + result.put(name, ddata); + } else if (value instanceof java.lang.Float) { + Float ddata = parseFloat((Float) value); + result.put(name, ddata); + } else if (value instanceof Number || value instanceof String || value instanceof Boolean) { + result.put(name, value); + } else if (value instanceof java.util.Date) { + result.put(name, value); + } else if (value instanceof CompositeData) { + CompositeData cdata = (CompositeData) value; + Set fieldKeys = cdata.getCompositeType().keySet(); + + for (String field : fieldKeys) { + if (field.length() < 1) { + continue; + } - private Map popResults() { - Map out = result; - result = new HashMap<>(); - return out; + String fieldKey = field.substring(0, 1).toUpperCase() + field.substring(1); + parseValue(String.format("%s.%s", name, fieldKey), cdata.get(field)); + } + } else if (value instanceof HashMap) { + // TODO: Process hashmaps + logger.fine("HashMaps are not supported yet: " + name); + } else if (value instanceof ArrayList || value.getClass().isArray()) { + // TODO: Process arrays + logger.fine("Arrays are not supported yet: " + name); + } else { + throw new ValueError("Unsuported data type (" + value.getClass() + ") for bean " + name); + } + } + + /** + * XXX: JSON does not support NaN, Infinity, or -Infinity as they come back from JMX. So we parse + * them out to 0, Max Double, and Min Double respectively. + */ + private Double parseDouble(Double value) { + if (value.isNaN()) { + return 0.0; + } else if (value == Double.NEGATIVE_INFINITY) { + return Double.MIN_VALUE; + } else if (value == Double.POSITIVE_INFINITY) { + return Double.MAX_VALUE; } - private void parseValue(String name, Object value) throws ValueError { - if (value == null) { - throw new ValueError("Found a null value for bean " + name); - } else if (value instanceof java.lang.Double) { - Double ddata = parseDouble((Double) value); - result.put(name, ddata); - } else if (value instanceof java.lang.Float) { - Float ddata = parseFloat((Float) value); - result.put(name, ddata); - } else if (value instanceof Number || value instanceof String || value instanceof Boolean) { - result.put(name, value); - } else if (value instanceof CompositeData) { - CompositeData cdata = (CompositeData) value; - Set fieldKeys = cdata.getCompositeType().keySet(); - - for (String field : fieldKeys) { - if (field.length() < 1) continue; - - String fieldKey = field.substring(0, 1).toUpperCase() + field.substring(1); - parseValue(String.format("%s.%s", name, fieldKey), cdata.get(field)); - } - } else if (value instanceof HashMap) { - // TODO: Process hashmaps - logger.fine("HashMaps are not supported yet: " + name); - } else if (value instanceof ArrayList || value.getClass().isArray()) { - // TODO: Process arrays - logger.fine("Arrays are not supported yet: " + name); - } else { - throw new ValueError("Unsuported data type (" + value.getClass() + ") for bean " + name); - } + return value; + } + + /** + * XXX: JSON does not support NaN, Infinity, or -Infinity as they come back from JMX. So we parse + * them out to 0, Max Double, and Min Double respectively. + */ + private Float parseFloat(Float value) { + if (value.isNaN()) { + return 0.0f; + } else if (value == Float.NEGATIVE_INFINITY) { + return Float.MIN_VALUE; + } else if (value == Float.POSITIVE_INFINITY) { + return Float.MAX_VALUE; } - /** - * XXX: JSON does not support NaN, Infinity, or -Infinity as they come back from JMX. - * So we parse them out to 0, Max Double, and Min Double respectively. - */ - private Double parseDouble(Double value) { - if (value.isNaN()) { - return 0.0; - } else if (value == Double.NEGATIVE_INFINITY) { - return Double.MIN_VALUE; - } else if (value == Double.POSITIVE_INFINITY) { - return Double.MAX_VALUE; - } + return value; + } - return value; + public class ConnectionError extends Exception { + + public ConnectionError(String message, Exception cause) { + super(message, cause); } + } - /** - * XXX: JSON does not support NaN, Infinity, or -Infinity as they come back from JMX. - * So we parse them out to 0, Max Double, and Min Double respectively. - */ - private Float parseFloat(Float value) { - if (value.isNaN()) { - return 0.0f; - } else if (value == Float.NEGATIVE_INFINITY) { - return Float.MIN_VALUE; - } else if (value == Float.POSITIVE_INFINITY) { - return Float.MAX_VALUE; - } + public class QueryError extends Exception { + + public QueryError(String message, Exception cause) { + super(message, cause); + } + } + + public class ValueError extends Exception { - return value; + public ValueError(String message) { + super(message); } + } } diff --git a/src/main/java/org/newrelic/nrjmx/Logging.java b/src/main/java/org/newrelic/nrjmx/Logging.java index f2957818..5ea66151 100644 --- a/src/main/java/org/newrelic/nrjmx/Logging.java +++ b/src/main/java/org/newrelic/nrjmx/Logging.java @@ -1,26 +1,31 @@ /* -* Copyright 2020 New Relic Corporation. All rights reserved. -* SPDX-License-Identifier: Apache-2.0 -*/ + * Copyright 2020 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ package org.newrelic.nrjmx; -import java.util.logging.*; +import java.util.logging.ConsoleHandler; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.logging.SimpleFormatter; public class Logging { - public static void setup(Logger logger, boolean verbose) { - logger.setUseParentHandlers(false); - Handler consoleHandler = new ConsoleHandler(); - logger.addHandler(consoleHandler); - consoleHandler.setFormatter(new SimpleFormatter()); + public static void setup(Logger logger, boolean verbose) { + logger.setUseParentHandlers(false); + Handler consoleHandler = new ConsoleHandler(); + logger.addHandler(consoleHandler); - if (verbose) { - logger.setLevel(Level.FINE); - consoleHandler.setLevel(Level.FINE); - } else { - logger.setLevel(Level.INFO); - consoleHandler.setLevel(Level.INFO); - } + consoleHandler.setFormatter(new SimpleFormatter()); + + if (verbose) { + logger.setLevel(Level.FINE); + consoleHandler.setLevel(Level.FINE); + } else { + logger.setLevel(Level.INFO); + consoleHandler.setLevel(Level.INFO); } + } }