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