001/** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.activemq.console.command; 018 019import java.io.File; 020import java.io.IOException; 021import java.lang.management.ManagementFactory; 022import java.lang.reflect.Method; 023import java.net.ConnectException; 024import java.net.MalformedURLException; 025import java.net.URL; 026import java.net.URLClassLoader; 027import java.util.HashMap; 028import java.util.List; 029import java.util.Map; 030import java.util.Properties; 031 032import javax.management.MBeanServerConnection; 033import javax.management.remote.JMXConnector; 034import javax.management.remote.JMXConnectorFactory; 035import javax.management.remote.JMXServiceURL; 036 037public abstract class AbstractJmxCommand extends AbstractCommand { 038 public static String DEFAULT_JMX_URL; 039 private static String jmxUser; 040 private static String jmxPassword; 041 private static boolean jmxUseLocal; 042 private static final String CONNECTOR_ADDRESS = 043 "com.sun.management.jmxremote.localConnectorAddress"; 044 045 private JMXServiceURL jmxServiceUrl; 046 private JMXConnector jmxConnector; 047 private MBeanServerConnection jmxConnection; 048 049 static { 050 DEFAULT_JMX_URL = System.getProperty("activemq.jmx.url", "service:jmx:rmi:///jndi/rmi://localhost:1099/jmxrmi"); 051 jmxUser = System.getProperty("activemq.jmx.user"); 052 jmxPassword = System.getProperty("activemq.jmx.password"); 053 jmxUseLocal = Boolean.parseBoolean(System.getProperty("activemq.jmx.useLocal", "false")); 054 } 055 056 /** 057 * Get the current specified JMX service url. 058 * @return JMX service url 059 */ 060 protected JMXServiceURL getJmxServiceUrl() { 061 return jmxServiceUrl; 062 } 063 064 public static String getJVM() { 065 return System.getProperty("java.vm.specification.vendor"); 066 } 067 068 public static boolean isSunJVM() { 069 // need to check for Oracle as that is the name for Java7 onwards. 070 return getJVM().equals("Sun Microsystems Inc.") || getJVM().startsWith("Oracle"); 071 } 072 073 /** 074 * Finds the JMX Url for a VM by its process id 075 * 076 * @param pid 077 * The process id value of the VM to search for. 078 * 079 * @return the JMX Url of the VM with the given pid or null if not found. 080 */ 081 @SuppressWarnings({ "rawtypes", "unchecked" }) 082 protected String findJMXUrlByProcessId(int pid) { 083 084 if (isSunJVM()) { 085 try { 086 // Classes are all dynamically loaded, since they are specific to Sun VM 087 // if it fails for any reason default jmx url will be used 088 089 // tools.jar are not always included used by default class loader, so we 090 // will try to use custom loader that will try to load tools.jar 091 092 String javaHome = System.getProperty("java.home"); 093 String tools = javaHome + File.separator + 094 ".." + File.separator + "lib" + File.separator + "tools.jar"; 095 URLClassLoader loader = new URLClassLoader(new URL[]{new File(tools).toURI().toURL()}); 096 097 Class virtualMachine = Class.forName("com.sun.tools.attach.VirtualMachine", true, loader); 098 Class virtualMachineDescriptor = Class.forName("com.sun.tools.attach.VirtualMachineDescriptor", true, loader); 099 100 Method getVMList = virtualMachine.getMethod("list", (Class[])null); 101 Method attachToVM = virtualMachine.getMethod("attach", String.class); 102 Method getAgentProperties = virtualMachine.getMethod("getAgentProperties", (Class[])null); 103 Method getVMId = virtualMachineDescriptor.getMethod("id", (Class[])null); 104 105 List allVMs = (List)getVMList.invoke(null, (Object[])null); 106 107 for(Object vmInstance : allVMs) { 108 String id = (String)getVMId.invoke(vmInstance, (Object[])null); 109 if (id.equals(Integer.toString(pid))) { 110 111 Object vm = attachToVM.invoke(null, id); 112 113 Properties agentProperties = (Properties)getAgentProperties.invoke(vm, (Object[])null); 114 String connectorAddress = agentProperties.getProperty(CONNECTOR_ADDRESS); 115 116 if (connectorAddress != null) { 117 return connectorAddress; 118 } else { 119 break; 120 } 121 } 122 } 123 } catch (Exception ignore) { 124 } 125 } 126 127 return null; 128 } 129 130 /** 131 * Get the current JMX service url being used, or create a default one if no JMX service url has been specified. 132 * @return JMX service url 133 * @throws MalformedURLException 134 */ 135 @SuppressWarnings({ "rawtypes", "unchecked" }) 136 protected JMXServiceURL useJmxServiceUrl() throws MalformedURLException { 137 if (getJmxServiceUrl() == null) { 138 String jmxUrl = DEFAULT_JMX_URL; 139 int connectingPid = -1; 140 if (isSunJVM()) { 141 try { 142 // Classes are all dynamically loaded, since they are specific to Sun VM 143 // if it fails for any reason default jmx url will be used 144 145 // tools.jar are not always included used by default class loader, so we 146 // will try to use custom loader that will try to load tools.jar 147 148 String javaHome = System.getProperty("java.home"); 149 String tools = javaHome + File.separator + 150 ".." + File.separator + "lib" + File.separator + "tools.jar"; 151 URLClassLoader loader = new URLClassLoader(new URL[]{new File(tools).toURI().toURL()}); 152 153 Class virtualMachine = Class.forName("com.sun.tools.attach.VirtualMachine", true, loader); 154 Class virtualMachineDescriptor = Class.forName("com.sun.tools.attach.VirtualMachineDescriptor", true, loader); 155 156 Method getVMList = virtualMachine.getMethod("list", (Class[])null); 157 Method attachToVM = virtualMachine.getMethod("attach", String.class); 158 Method getAgentProperties = virtualMachine.getMethod("getAgentProperties", (Class[])null); 159 Method getVMDescriptor = virtualMachineDescriptor.getMethod("displayName", (Class[])null); 160 Method getVMId = virtualMachineDescriptor.getMethod("id", (Class[])null); 161 162 List allVMs = (List)getVMList.invoke(null, (Object[])null); 163 164 for(Object vmInstance : allVMs) { 165 String displayName = (String)getVMDescriptor.invoke(vmInstance, (Object[])null); 166 if (displayName.contains("activemq.jar start")) { 167 String id = (String)getVMId.invoke(vmInstance, (Object[])null); 168 169 Object vm = attachToVM.invoke(null, id); 170 171 Properties agentProperties = (Properties)getAgentProperties.invoke(vm, (Object[])null); 172 String connectorAddress = agentProperties.getProperty(CONNECTOR_ADDRESS); 173 174 if (connectorAddress != null) { 175 jmxUrl = connectorAddress; 176 connectingPid = Integer.parseInt(id); 177 context.print("useJmxServiceUrl Found JMS Url: " + jmxUrl); 178 break; 179 } 180 } 181 } 182 } catch (Exception ignore) { 183 } 184 } 185 186 if (connectingPid != -1) { 187 context.print("Connecting to pid: " + connectingPid); 188 } else { 189 context.print("Connecting to JMX URL: " + jmxUrl); 190 } 191 setJmxServiceUrl(jmxUrl); 192 } 193 194 return getJmxServiceUrl(); 195 } 196 197 /** 198 * Sets the JMX service url to use. 199 * @param jmxServiceUrl - new JMX service url to use 200 */ 201 protected void setJmxServiceUrl(JMXServiceURL jmxServiceUrl) { 202 this.jmxServiceUrl = jmxServiceUrl; 203 } 204 205 /** 206 * Sets the JMX service url to use. 207 * @param jmxServiceUrl - new JMX service url to use 208 * @throws MalformedURLException 209 */ 210 protected void setJmxServiceUrl(String jmxServiceUrl) throws MalformedURLException { 211 setJmxServiceUrl(new JMXServiceURL(jmxServiceUrl)); 212 } 213 214 /** 215 * Get the JMX user name to be used when authenticating. 216 * @return the JMX user name 217 */ 218 public String getJmxUser() { 219 return jmxUser; 220 } 221 222 /** 223 * Sets the JMS user name to use 224 * @param jmxUser - the jmx 225 */ 226 public void setJmxUser(String jmxUser) { 227 AbstractJmxCommand.jmxUser = jmxUser; 228 } 229 230 /** 231 * Get the password used when authenticating 232 * @return the password used for JMX authentication 233 */ 234 public String getJmxPassword() { 235 return jmxPassword; 236 } 237 238 /** 239 * Sets the password to use when authenticating 240 * @param jmxPassword - the password used for JMX authentication 241 */ 242 public void setJmxPassword(String jmxPassword) { 243 AbstractJmxCommand.jmxPassword = jmxPassword; 244 } 245 246 /** 247 * Get whether the default mbean server for this JVM should be used instead of the jmx url 248 * @return <code>true</code> if the mbean server from this JVM should be used, <code>false<code> if the jmx url should be used 249 */ 250 public boolean isJmxUseLocal() { 251 return jmxUseLocal; 252 } 253 254 /** 255 * Sets whether the the default mbean server for this JVM should be used instead of the jmx url 256 * @param jmxUseLocal - <code>true</code> if the mbean server from this JVM should be used, <code>false<code> if the jmx url should be used 257 */ 258 public void setJmxUseLocal(boolean jmxUseLocal) { 259 AbstractJmxCommand.jmxUseLocal = jmxUseLocal; 260 } 261 262 /** 263 * Create a JMX connector using the current specified JMX service url. If there is an existing connection, 264 * it tries to reuse this connection. 265 * @return created JMX connector 266 * @throws IOException 267 */ 268 private JMXConnector createJmxConnector() throws IOException { 269 // Reuse the previous connection 270 if (jmxConnector != null) { 271 jmxConnector.connect(); 272 return jmxConnector; 273 } 274 275 // Create a new JMX connector 276 if (jmxUser != null && jmxPassword != null) { 277 Map<String,Object> props = new HashMap<String,Object>(); 278 props.put(JMXConnector.CREDENTIALS, new String[] { jmxUser, jmxPassword }); 279 jmxConnector = JMXConnectorFactory.connect(useJmxServiceUrl(), props); 280 } else { 281 jmxConnector = JMXConnectorFactory.connect(useJmxServiceUrl()); 282 } 283 return jmxConnector; 284 } 285 286 /** 287 * Close the current JMX connector 288 */ 289 protected void closeJmxConnection() { 290 try { 291 if (jmxConnector != null) { 292 jmxConnector.close(); 293 jmxConnector = null; 294 } 295 } catch (IOException e) { 296 } 297 } 298 299 protected MBeanServerConnection createJmxConnection() throws IOException { 300 if (jmxConnection == null) { 301 if (isJmxUseLocal()) { 302 jmxConnection = ManagementFactory.getPlatformMBeanServer(); 303 } else { 304 jmxConnection = createJmxConnector().getMBeanServerConnection(); 305 } 306 } 307 return jmxConnection; 308 } 309 310 /** 311 * Handle the --jmxurl option. 312 * @param token - option token to handle 313 * @param tokens - succeeding command arguments 314 * @throws Exception 315 */ 316 @Override 317 protected void handleOption(String token, List<String> tokens) throws Exception { 318 // Try to handle the options first 319 if (token.equals("--jmxurl")) { 320 // If no jmx url specified, or next token is a new option 321 if (tokens.isEmpty() || tokens.get(0).startsWith("-")) { 322 context.printException(new IllegalArgumentException("JMX URL not specified.")); 323 } 324 325 // If jmx url already specified 326 if (getJmxServiceUrl() != null) { 327 context.printException(new IllegalArgumentException("Multiple JMX URL cannot be specified.")); 328 tokens.clear(); 329 } 330 331 String strJmxUrl = tokens.remove(0); 332 try { 333 this.setJmxServiceUrl(new JMXServiceURL(strJmxUrl)); 334 } catch (MalformedURLException e) { 335 context.printException(e); 336 tokens.clear(); 337 } 338 } else if(token.equals("--pid")) { 339 if (isSunJVM()) { 340 if (tokens.isEmpty() || tokens.get(0).startsWith("-")) { 341 context.printException(new IllegalArgumentException("pid not specified")); 342 return; 343 } 344 int pid = Integer.parseInt(tokens.remove(0)); 345 context.print("Connecting to pid: " + pid); 346 347 String jmxUrl = findJMXUrlByProcessId(pid); 348 if (jmxUrl != null) { 349 // If jmx url already specified 350 if (getJmxServiceUrl() != null) { 351 context.printException(new IllegalArgumentException("JMX URL already specified.")); 352 tokens.clear(); 353 } 354 try { 355 this.setJmxServiceUrl(new JMXServiceURL(jmxUrl)); 356 } catch (MalformedURLException e) { 357 context.printException(e); 358 tokens.clear(); 359 } 360 } else { 361 context.printInfo("failed to resolve jmxUrl for pid:" + pid + ", using default JMX url"); 362 } 363 } else { 364 context.printInfo("--pid option is not available for this VM, using default JMX url"); 365 } 366 } else if (token.equals("--jmxuser")) { 367 // If no jmx user specified, or next token is a new option 368 if (tokens.isEmpty() || tokens.get(0).startsWith("-")) { 369 context.printException(new IllegalArgumentException("JMX user not specified.")); 370 } 371 this.setJmxUser(tokens.remove(0)); 372 } else if (token.equals("--jmxpassword")) { 373 // If no jmx password specified, or next token is a new option 374 if (tokens.isEmpty() || tokens.get(0).startsWith("-")) { 375 context.printException(new IllegalArgumentException("JMX password not specified.")); 376 } 377 this.setJmxPassword(tokens.remove(0)); 378 } else if (token.equals("--jmxlocal")) { 379 this.setJmxUseLocal(true); 380 } else { 381 // Let the super class handle the option 382 super.handleOption(token, tokens); 383 } 384 } 385 386 @Override 387 public void execute(List<String> tokens) throws Exception { 388 try { 389 super.execute(tokens); 390 } catch (Exception exception) { 391 handleException(exception, jmxServiceUrl.toString()); 392 return; 393 }finally { 394 closeJmxConnection(); 395 } 396 } 397}