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