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 24 package jdk.testlibrary.dcmd; 25 26 import jdk.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 }