JMXExecutor.java revision 2224:2a8815d86b93
151829Smdodd/*
251829Smdodd * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
351829Smdodd * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
451829Smdodd *
551829Smdodd * This code is free software; you can redistribute it and/or modify it
651829Smdodd * under the terms of the GNU General Public License version 2 only, as
751829Smdodd * published by the Free Software Foundation.
851829Smdodd *
951829Smdodd * This code is distributed in the hope that it will be useful, but WITHOUT
1051829Smdodd * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
1151829Smdodd * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
1251829Smdodd * version 2 for more details (a copy is included in the LICENSE file that
1351829Smdodd * accompanied this code).
1451829Smdodd *
1551829Smdodd * You should have received a copy of the GNU General Public License version
1651829Smdodd * 2 along with this work; if not, write to the Free Software Foundation,
1751829Smdodd * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
1851829Smdodd *
1951829Smdodd * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2051829Smdodd * or visit www.oracle.com if you need additional information or have any
2151829Smdodd * questions.
2251829Smdodd */
2351829Smdodd
2451829Smdoddpackage jdk.test.lib.dcmd;
2551829Smdodd
2651829Smdoddimport jdk.test.lib.process.OutputAnalyzer;
2751829Smdodd
2851829Smdoddimport javax.management.*;
29119418Sobrienimport javax.management.remote.JMXConnector;
30119418Sobrienimport javax.management.remote.JMXConnectorFactory;
31119418Sobrienimport javax.management.remote.JMXServiceURL;
3251829Smdodd
3351829Smdoddimport java.io.IOException;
34241611Spluknetimport java.io.PrintWriter;
3551829Smdoddimport java.io.StringWriter;
36117126Sscottl
37117126Sscottlimport java.lang.management.ManagementFactory;
3851829Smdodd
3951829Smdoddimport java.util.HashMap;
4051829Smdodd
4151829Smdodd/**
4251829Smdodd * Executes Diagnostic Commands on the target VM (specified by a host/port combination or a full JMX Service URL) using
4351829Smdodd * the JMX interface. If the target is not the current VM, the JMX Remote interface must be enabled beforehand.
4451829Smdodd */
45135260Sphkpublic class JMXExecutor extends CommandExecutor {
4651829Smdodd
4751829Smdodd    private final MBeanServerConnection mbs;
4851829Smdodd
4951829Smdodd    /**
5051829Smdodd     * Instantiates a new JMXExecutor targeting the current VM
5151829Smdodd     */
5251829Smdodd    public JMXExecutor() {
5351829Smdodd        super();
5451829Smdodd        mbs = ManagementFactory.getPlatformMBeanServer();
5551829Smdodd    }
5651829Smdodd
5751829Smdodd    /**
5851829Smdodd     * Instantiates a new JMXExecutor targeting the VM indicated by the given host/port combination or a full JMX
5951829Smdodd     * Service URL
6051829Smdodd     *
6151829Smdodd     * @param target a host/port combination on the format "host:port" or a full JMX Service URL of the target VM
62122340Simp     */
6351829Smdodd    public JMXExecutor(String target) {
64122340Simp        String urlStr;
6551829Smdodd
6651829Smdodd        if (target.matches("^\\w[\\w\\-]*(\\.[\\w\\-]+)*:\\d+$")) {
6751829Smdodd            /* Matches "hostname:port" */
6851829Smdodd            urlStr = String.format("service:jmx:rmi:///jndi/rmi://%s/jmxrmi", target);
6951829Smdodd        } else if (target.startsWith("service:")) {
7051829Smdodd            urlStr = target;
7151829Smdodd        } else {
7251829Smdodd            throw new IllegalArgumentException("Could not recognize target string: " + target);
7351829Smdodd        }
7451829Smdodd
7551829Smdodd        try {
7651829Smdodd            JMXServiceURL url = new JMXServiceURL(urlStr);
7751829Smdodd            JMXConnector c = JMXConnectorFactory.connect(url, new HashMap<>());
7851829Smdodd            mbs = c.getMBeanServerConnection();
7951829Smdodd        } catch (IOException e) {
8051829Smdodd            throw new CommandExecutorException("Could not initiate connection to target: " + target, e);
8151829Smdodd        }
8251829Smdodd    }
8351829Smdodd
8451829Smdodd    protected OutputAnalyzer executeImpl(String cmd) throws CommandExecutorException {
8551829Smdodd        String stdout = "";
8651829Smdodd        String stderr = "";
8751829Smdodd
8851829Smdodd        String[] cmdParts = cmd.split(" ", 2);
89122340Simp        String operation = commandToMethodName(cmdParts[0]);
90122340Simp        Object[] dcmdArgs = produceArguments(cmdParts);
91140467Simp        String[] signature = {String[].class.getName()};
92140467Simp
93140467Simp        ObjectName beanName = getMBeanName();
9451829Smdodd
9551829Smdodd        try {
9651829Smdodd            stdout = (String) mbs.invoke(beanName, operation, dcmdArgs, signature);
9751829Smdodd        }
9851829Smdodd
9951829Smdodd        /* Failures on the "local" side, the one invoking the command. */
10051829Smdodd        catch (ReflectionException e) {
10151829Smdodd            Throwable cause = e.getCause();
10251829Smdodd            if (cause instanceof NoSuchMethodException) {
10351829Smdodd                /* We want JMXExecutor to match the behavior of the other CommandExecutors */
10451829Smdodd                String message = "Unknown diagnostic command: " + operation;
10551829Smdodd                stderr = exceptionTraceAsString(new IllegalArgumentException(message, e));
10651829Smdodd            } else {
10751829Smdodd                rethrowExecutorException(operation, dcmdArgs, e);
10851829Smdodd            }
10951829Smdodd        }
11051829Smdodd
11151829Smdodd        /* Failures on the "local" side, the one invoking the command. */
11251829Smdodd        catch (InstanceNotFoundException | IOException e) {
11351829Smdodd            rethrowExecutorException(operation, dcmdArgs, e);
11451829Smdodd        }
11551829Smdodd
11651829Smdodd        /* Failures on the remote side, the one executing the invoked command. */
11751829Smdodd        catch (MBeanException e) {
11851829Smdodd            stdout = exceptionTraceAsString(e);
11951829Smdodd        }
120122361Simp
121140467Simp        return new OutputAnalyzer(stdout, stderr);
12251829Smdodd    }
123140467Simp
124140467Simp    private void rethrowExecutorException(String operation, Object[] dcmdArgs,
125140467Simp                                          Exception e) throws CommandExecutorException {
126140467Simp        String message = String.format("Could not invoke: %s %s", operation,
12751829Smdodd                String.join(" ", (String[]) dcmdArgs[0]));
12851829Smdodd        throw new CommandExecutorException(message, e);
12951829Smdodd    }
13051829Smdodd
131140467Simp    private ObjectName getMBeanName() throws CommandExecutorException {
132140467Simp        String MBeanName = "com.sun.management:type=DiagnosticCommand";
133140467Simp
134140467Simp        try {
13551829Smdodd            return new ObjectName(MBeanName);
13651829Smdodd        } catch (MalformedObjectNameException e) {
13751829Smdodd            String message = "MBean not found: " + MBeanName;
13851829Smdodd            throw new CommandExecutorException(message, e);
139140467Simp        }
140140467Simp    }
141140467Simp
142140467Simp    private Object[] produceArguments(String[] cmdParts) {
14351829Smdodd        Object[] dcmdArgs = {new String[0]}; /* Default: No arguments */
14451829Smdodd
14551829Smdodd        if (cmdParts.length == 2) {
14651829Smdodd            dcmdArgs[0] = cmdParts[1].split(" ");
147241589Sjhb        }
14851829Smdodd        return dcmdArgs;
14951829Smdodd    }
15051829Smdodd
15151829Smdodd    /**
15251829Smdodd     * Convert from diagnostic command to MBean method name
15351829Smdodd     *
15451829Smdodd     * Examples:
15551829Smdodd     * help            --> help
15651829Smdodd     * VM.version      --> vmVersion
15751829Smdodd     * VM.command_line --> vmCommandLine
15851829Smdodd     */
15951829Smdodd    private static String commandToMethodName(String cmd) {
160140467Simp        String operation = "";
16151829Smdodd        boolean up = false; /* First letter is to be lower case */
162112782Smdodd
163183678Simp        /*
164112782Smdodd         * If a '.' or '_' is encountered it is not copied,
165112782Smdodd         * instead the next character will be converted to upper case
166112782Smdodd         */
167112782Smdodd        for (char c : cmd.toCharArray()) {
168112782Smdodd            if (('.' == c) || ('_' == c)) {
169112782Smdodd                up = true;
170112782Smdodd            } else if (up) {
171112782Smdodd                operation = operation.concat(Character.toString(c).toUpperCase());
172112782Smdodd                up = false;
173112782Smdodd            } else {
174241589Sjhb                operation = operation.concat(Character.toString(c).toLowerCase());
175241589Sjhb            }
176112782Smdodd        }
17751829Smdodd
17851829Smdodd        return operation;
17951829Smdodd    }
18051829Smdodd
18151829Smdodd    private static String exceptionTraceAsString(Throwable cause) {
18251829Smdodd        StringWriter sw = new StringWriter();
18351829Smdodd        cause.printStackTrace(new PrintWriter(sw));
18451829Smdodd        return sw.toString();
18551829Smdodd    }
18651829Smdodd
18751829Smdodd}
18851829Smdodd