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