JMXExecutor.java revision 2224:2a8815d86b93
1/*
2 * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24package jdk.test.lib.dcmd;
25
26import jdk.test.lib.process.OutputAnalyzer;
27
28import javax.management.*;
29import javax.management.remote.JMXConnector;
30import javax.management.remote.JMXConnectorFactory;
31import javax.management.remote.JMXServiceURL;
32
33import java.io.IOException;
34import java.io.PrintWriter;
35import java.io.StringWriter;
36
37import java.lang.management.ManagementFactory;
38
39import java.util.HashMap;
40
41/**
42 * Executes Diagnostic Commands on the target VM (specified by a host/port combination or a full JMX Service URL) using
43 * the JMX interface. If the target is not the current VM, the JMX Remote interface must be enabled beforehand.
44 */
45public class JMXExecutor extends CommandExecutor {
46
47    private final MBeanServerConnection mbs;
48
49    /**
50     * Instantiates a new JMXExecutor targeting the current VM
51     */
52    public JMXExecutor() {
53        super();
54        mbs = ManagementFactory.getPlatformMBeanServer();
55    }
56
57    /**
58     * Instantiates a new JMXExecutor targeting the VM indicated by the given host/port combination or a full JMX
59     * Service URL
60     *
61     * @param target a host/port combination on the format "host:port" or a full JMX Service URL of the target VM
62     */
63    public JMXExecutor(String target) {
64        String urlStr;
65
66        if (target.matches("^\\w[\\w\\-]*(\\.[\\w\\-]+)*:\\d+$")) {
67            /* Matches "hostname:port" */
68            urlStr = String.format("service:jmx:rmi:///jndi/rmi://%s/jmxrmi", target);
69        } else if (target.startsWith("service:")) {
70            urlStr = target;
71        } else {
72            throw new IllegalArgumentException("Could not recognize target string: " + target);
73        }
74
75        try {
76            JMXServiceURL url = new JMXServiceURL(urlStr);
77            JMXConnector c = JMXConnectorFactory.connect(url, new HashMap<>());
78            mbs = c.getMBeanServerConnection();
79        } catch (IOException e) {
80            throw new CommandExecutorException("Could not initiate connection to target: " + target, e);
81        }
82    }
83
84    protected OutputAnalyzer executeImpl(String cmd) throws CommandExecutorException {
85        String stdout = "";
86        String stderr = "";
87
88        String[] cmdParts = cmd.split(" ", 2);
89        String operation = commandToMethodName(cmdParts[0]);
90        Object[] dcmdArgs = produceArguments(cmdParts);
91        String[] signature = {String[].class.getName()};
92
93        ObjectName beanName = getMBeanName();
94
95        try {
96            stdout = (String) mbs.invoke(beanName, operation, dcmdArgs, signature);
97        }
98
99        /* Failures on the "local" side, the one invoking the command. */
100        catch (ReflectionException e) {
101            Throwable cause = e.getCause();
102            if (cause instanceof NoSuchMethodException) {
103                /* We want JMXExecutor to match the behavior of the other CommandExecutors */
104                String message = "Unknown diagnostic command: " + operation;
105                stderr = exceptionTraceAsString(new IllegalArgumentException(message, e));
106            } else {
107                rethrowExecutorException(operation, dcmdArgs, e);
108            }
109        }
110
111        /* Failures on the "local" side, the one invoking the command. */
112        catch (InstanceNotFoundException | IOException e) {
113            rethrowExecutorException(operation, dcmdArgs, e);
114        }
115
116        /* Failures on the remote side, the one executing the invoked command. */
117        catch (MBeanException e) {
118            stdout = exceptionTraceAsString(e);
119        }
120
121        return new OutputAnalyzer(stdout, stderr);
122    }
123
124    private void rethrowExecutorException(String operation, Object[] dcmdArgs,
125                                          Exception e) throws CommandExecutorException {
126        String message = String.format("Could not invoke: %s %s", operation,
127                String.join(" ", (String[]) dcmdArgs[0]));
128        throw new CommandExecutorException(message, e);
129    }
130
131    private ObjectName getMBeanName() throws CommandExecutorException {
132        String MBeanName = "com.sun.management:type=DiagnosticCommand";
133
134        try {
135            return new ObjectName(MBeanName);
136        } catch (MalformedObjectNameException e) {
137            String message = "MBean not found: " + MBeanName;
138            throw new CommandExecutorException(message, e);
139        }
140    }
141
142    private Object[] produceArguments(String[] cmdParts) {
143        Object[] dcmdArgs = {new String[0]}; /* Default: No arguments */
144
145        if (cmdParts.length == 2) {
146            dcmdArgs[0] = cmdParts[1].split(" ");
147        }
148        return dcmdArgs;
149    }
150
151    /**
152     * Convert from diagnostic command to MBean method name
153     *
154     * Examples:
155     * help            --> help
156     * VM.version      --> vmVersion
157     * VM.command_line --> vmCommandLine
158     */
159    private static String commandToMethodName(String cmd) {
160        String operation = "";
161        boolean up = false; /* First letter is to be lower case */
162
163        /*
164         * If a '.' or '_' is encountered it is not copied,
165         * instead the next character will be converted to upper case
166         */
167        for (char c : cmd.toCharArray()) {
168            if (('.' == c) || ('_' == c)) {
169                up = true;
170            } else if (up) {
171                operation = operation.concat(Character.toString(c).toUpperCase());
172                up = false;
173            } else {
174                operation = operation.concat(Character.toString(c).toLowerCase());
175            }
176        }
177
178        return operation;
179    }
180
181    private static String exceptionTraceAsString(Throwable cause) {
182        StringWriter sw = new StringWriter();
183        cause.printStackTrace(new PrintWriter(sw));
184        return sw.toString();
185    }
186
187}
188