|
1 /* |
|
2 * Copyright (c) 2015, 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 |
|
24 package com.oracle.java.testlibrary.dcmd; |
|
25 |
|
26 import com.oracle.java.testlibrary.OutputAnalyzer; |
|
27 |
|
28 import javax.management.*; |
|
29 import javax.management.remote.JMXConnector; |
|
30 import javax.management.remote.JMXConnectorFactory; |
|
31 import javax.management.remote.JMXServiceURL; |
|
32 |
|
33 import java.io.IOException; |
|
34 import java.io.PrintWriter; |
|
35 import java.io.StringWriter; |
|
36 |
|
37 import java.lang.management.ManagementFactory; |
|
38 |
|
39 import 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 */ |
|
45 public 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 } |