blob: f0a8d3f02516ce717f129f18383829ab22f67c87 [file] [log] [blame]
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code 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
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.test.lib.dcmd;
import jdk.test.lib.OutputAnalyzer;
import javax.management.*;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.management.ManagementFactory;
import java.util.HashMap;
/**
* Executes Diagnostic Commands on the target VM (specified by a host/port combination or a full JMX Service URL) using
* the JMX interface. If the target is not the current VM, the JMX Remote interface must be enabled beforehand.
*/
public class JMXExecutor extends CommandExecutor {
private final MBeanServerConnection mbs;
/**
* Instantiates a new JMXExecutor targeting the current VM
*/
public JMXExecutor() {
super();
mbs = ManagementFactory.getPlatformMBeanServer();
}
/**
* Instantiates a new JMXExecutor targeting the VM indicated by the given host/port combination or a full JMX
* Service URL
*
* @param target a host/port combination on the format "host:port" or a full JMX Service URL of the target VM
*/
public JMXExecutor(String target) {
String urlStr;
if (target.matches("^\\w[\\w\\-]*(\\.[\\w\\-]+)*:\\d+$")) {
/* Matches "hostname:port" */
urlStr = String.format("service:jmx:rmi:///jndi/rmi://%s/jmxrmi", target);
} else if (target.startsWith("service:")) {
urlStr = target;
} else {
throw new IllegalArgumentException("Could not recognize target string: " + target);
}
try {
JMXServiceURL url = new JMXServiceURL(urlStr);
JMXConnector c = JMXConnectorFactory.connect(url, new HashMap<>());
mbs = c.getMBeanServerConnection();
} catch (IOException e) {
throw new CommandExecutorException("Could not initiate connection to target: " + target, e);
}
}
protected OutputAnalyzer executeImpl(String cmd) throws CommandExecutorException {
String stdout = "";
String stderr = "";
String[] cmdParts = cmd.split(" ", 2);
String operation = commandToMethodName(cmdParts[0]);
Object[] dcmdArgs = produceArguments(cmdParts);
String[] signature = {String[].class.getName()};
ObjectName beanName = getMBeanName();
try {
stdout = (String) mbs.invoke(beanName, operation, dcmdArgs, signature);
}
/* Failures on the "local" side, the one invoking the command. */
catch (ReflectionException e) {
Throwable cause = e.getCause();
if (cause instanceof NoSuchMethodException) {
/* We want JMXExecutor to match the behavior of the other CommandExecutors */
String message = "Unknown diagnostic command: " + operation;
stderr = exceptionTraceAsString(new IllegalArgumentException(message, e));
} else {
rethrowExecutorException(operation, dcmdArgs, e);
}
}
/* Failures on the "local" side, the one invoking the command. */
catch (InstanceNotFoundException | IOException e) {
rethrowExecutorException(operation, dcmdArgs, e);
}
/* Failures on the remote side, the one executing the invoked command. */
catch (MBeanException e) {
stdout = exceptionTraceAsString(e);
}
return new OutputAnalyzer(stdout, stderr);
}
private void rethrowExecutorException(String operation, Object[] dcmdArgs,
Exception e) throws CommandExecutorException {
String message = String.format("Could not invoke: %s %s", operation,
String.join(" ", (String[]) dcmdArgs[0]));
throw new CommandExecutorException(message, e);
}
private ObjectName getMBeanName() throws CommandExecutorException {
String MBeanName = "com.sun.management:type=DiagnosticCommand";
try {
return new ObjectName(MBeanName);
} catch (MalformedObjectNameException e) {
String message = "MBean not found: " + MBeanName;
throw new CommandExecutorException(message, e);
}
}
private Object[] produceArguments(String[] cmdParts) {
Object[] dcmdArgs = {new String[0]}; /* Default: No arguments */
if (cmdParts.length == 2) {
dcmdArgs[0] = cmdParts[1].split(" ");
}
return dcmdArgs;
}
/**
* Convert from diagnostic command to MBean method name
*
* Examples:
* help --> help
* VM.version --> vmVersion
* VM.command_line --> vmCommandLine
*/
private static String commandToMethodName(String cmd) {
String operation = "";
boolean up = false; /* First letter is to be lower case */
/*
* If a '.' or '_' is encountered it is not copied,
* instead the next character will be converted to upper case
*/
for (char c : cmd.toCharArray()) {
if (('.' == c) || ('_' == c)) {
up = true;
} else if (up) {
operation = operation.concat(Character.toString(c).toUpperCase());
up = false;
} else {
operation = operation.concat(Character.toString(c).toLowerCase());
}
}
return operation;
}
private static String exceptionTraceAsString(Throwable cause) {
StringWriter sw = new StringWriter();
cause.printStackTrace(new PrintWriter(sw));
return sw.toString();
}
}