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.Constructor; 023 import java.lang.reflect.Method; 024 import java.net.MalformedURLException; 025 import java.net.URL; 026 import java.net.URLClassLoader; 027 import java.util.HashMap; 028 import java.util.List; 029 import java.util.Map; 030 import java.util.Set; 031 032 import javax.management.MBeanServerConnection; 033 import javax.management.remote.JMXConnector; 034 import javax.management.remote.JMXConnectorFactory; 035 import javax.management.remote.JMXServiceURL; 036 037 import sun.management.ConnectorAddressLink; 038 039 public abstract class AbstractJmxCommand extends AbstractCommand { 040 public static final String DEFAULT_JMX_URL = "service:jmx:rmi:///jndi/rmi://localhost:1099/jmxrmi"; 041 042 private JMXServiceURL jmxServiceUrl; 043 private String jmxUser; 044 private String jmxPassword; 045 private boolean jmxUseLocal; 046 private JMXConnector jmxConnector; 047 private MBeanServerConnection jmxConnection; 048 049 /** 050 * Get the current specified JMX service url. 051 * @return JMX service url 052 */ 053 protected JMXServiceURL getJmxServiceUrl() { 054 return jmxServiceUrl; 055 } 056 057 public static String getJVM() { 058 return System.getProperty("java.vm.specification.vendor"); 059 } 060 061 public static boolean isSunJVM() { 062 return getJVM().equals("Sun Microsystems Inc."); 063 } 064 065 /** 066 * Get the current JMX service url being used, or create a default one if no JMX service url has been specified. 067 * @return JMX service url 068 * @throws MalformedURLException 069 */ 070 protected JMXServiceURL useJmxServiceUrl() throws MalformedURLException { 071 if (getJmxServiceUrl() == null) { 072 String jmxUrl = DEFAULT_JMX_URL; 073 int connectingPid = -1; 074 if (isSunJVM()) { 075 try { 076 // Try to attach to the local process 077 // Classes are all dynamically loaded, since they are specific to Sun VM 078 // if it fails for any reason default jmx url will be used 079 080 081 // tools.jar are not always included used by default 082 // class loader, so we will try to use custom loader that will 083 // try to load tools.jar 084 String javaHome = System.getProperty("java.home"); 085 String tools = javaHome + File.separator + 086 ".." + File.separator + "lib" + File.separator + "tools.jar"; 087 URLClassLoader loader = new URLClassLoader(new URL[]{new File(tools).toURI().toURL()}); 088 089 // load all classes dynamically so we can compile on non-Sun VMs 090 091 //MonitoredHost host = MonitoredHost.getMonitoredHost(new HostIdentifier((String)null)); 092 Class monitoredHostClass = Class.forName("sun.jvmstat.monitor.MonitoredHost", true, loader); 093 Method getMonitoredHostMethod = monitoredHostClass.getMethod("getMonitoredHost", String.class); 094 Object host = getMonitoredHostMethod.invoke(null, (String)null); 095 //Set vms = host.activeVms(); 096 Method activeVmsMethod = host.getClass().getMethod("activeVms", null); 097 Set vms = (Set)activeVmsMethod.invoke(host, null); 098 for (Object vmid: vms) { 099 int pid = ((Integer) vmid).intValue(); 100 //MonitoredVm mvm = host.getMonitoredVm(new VmIdentifier(vmid.toString())); 101 Class vmIdentifierClass = Class.forName("sun.jvmstat.monitor.VmIdentifier", true, loader); 102 Constructor vmIdentifierConstructor = vmIdentifierClass.getConstructor(String.class); 103 Object vmIdentifier = vmIdentifierConstructor.newInstance(vmid.toString()); 104 Method getMonitoredVmMethod = host.getClass().getMethod("getMonitoredVm", vmIdentifierClass); 105 Object mvm = getMonitoredVmMethod.invoke(host, vmIdentifier); 106 //String name = MonitoredVmUtil.commandLine(mvm); 107 Class monitoredVmUtilClass = Class.forName("sun.jvmstat.monitor.MonitoredVmUtil", true, loader); 108 Method commandLineMethod = monitoredVmUtilClass.getMethod("commandLine", Class.forName("sun.jvmstat.monitor.MonitoredVm", true, loader)); 109 String name = (String)commandLineMethod.invoke(null, mvm); 110 if (name.contains("run.jar start")) { 111 connectingPid = pid; 112 jmxUrl = ConnectorAddressLink.importFrom(pid); 113 break; 114 } 115 } 116 } catch (Exception ignore) {} 117 } 118 119 if (connectingPid != -1) { 120 context.print("Connecting to pid: " + connectingPid); 121 } else { 122 context.print("Connecting to JMX URL: " + jmxUrl); 123 } 124 setJmxServiceUrl(jmxUrl); 125 } 126 127 return getJmxServiceUrl(); 128 } 129 130 /** 131 * Sets the JMX service url to use. 132 * @param jmxServiceUrl - new JMX service url to use 133 */ 134 protected void setJmxServiceUrl(JMXServiceURL jmxServiceUrl) { 135 this.jmxServiceUrl = jmxServiceUrl; 136 } 137 138 /** 139 * Sets the JMX service url to use. 140 * @param jmxServiceUrl - new JMX service url to use 141 * @throws MalformedURLException 142 */ 143 protected void setJmxServiceUrl(String jmxServiceUrl) throws MalformedURLException { 144 setJmxServiceUrl(new JMXServiceURL(jmxServiceUrl)); 145 } 146 147 /** 148 * Get the JMX user name to be used when authenticating. 149 * @return the JMX user name 150 */ 151 public String getJmxUser() { 152 return jmxUser; 153 } 154 155 /** 156 * Sets the JMS user name to use 157 * @param jmxUser - the jmx 158 */ 159 public void setJmxUser(String jmxUser) { 160 this.jmxUser = jmxUser; 161 } 162 163 /** 164 * Get the password used when authenticating 165 * @return the password used for JMX authentication 166 */ 167 public String getJmxPassword() { 168 return jmxPassword; 169 } 170 171 /** 172 * Sets the password to use when authenticating 173 * @param jmxPassword - the password used for JMX authentication 174 */ 175 public void setJmxPassword(String jmxPassword) { 176 this.jmxPassword = jmxPassword; 177 } 178 179 /** 180 * Get whether the default mbean server for this JVM should be used instead of the jmx url 181 * @return <code>true</code> if the mbean server from this JVM should be used, <code>false<code> if the jmx url should be used 182 */ 183 public boolean isJmxUseLocal() { 184 return jmxUseLocal; 185 } 186 187 /** 188 * Sets whether the the default mbean server for this JVM should be used instead of the jmx url 189 * @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 190 */ 191 public void setJmxUseLocal(boolean jmxUseLocal) { 192 this.jmxUseLocal = jmxUseLocal; 193 } 194 195 /** 196 * Create a JMX connector using the current specified JMX service url. If there is an existing connection, 197 * it tries to reuse this connection. 198 * @return created JMX connector 199 * @throws IOException 200 */ 201 private JMXConnector createJmxConnector() throws IOException { 202 // Reuse the previous connection 203 if (jmxConnector != null) { 204 jmxConnector.connect(); 205 return jmxConnector; 206 } 207 208 // Create a new JMX connector 209 if (jmxUser != null && jmxPassword != null) { 210 Map<String,Object> props = new HashMap<String,Object>(); 211 props.put(JMXConnector.CREDENTIALS, new String[] { jmxUser, jmxPassword }); 212 jmxConnector = JMXConnectorFactory.connect(useJmxServiceUrl(), props); 213 } else { 214 jmxConnector = JMXConnectorFactory.connect(useJmxServiceUrl()); 215 } 216 return jmxConnector; 217 } 218 219 /** 220 * Close the current JMX connector 221 */ 222 protected void closeJmxConnection() { 223 try { 224 if (jmxConnector != null) { 225 jmxConnector.close(); 226 jmxConnector = null; 227 } 228 } catch (IOException e) { 229 } 230 } 231 232 protected MBeanServerConnection createJmxConnection() throws IOException { 233 if (jmxConnection == null) { 234 if (isJmxUseLocal()) { 235 jmxConnection = ManagementFactory.getPlatformMBeanServer(); 236 } else { 237 jmxConnection = createJmxConnector().getMBeanServerConnection(); 238 } 239 } 240 return jmxConnection; 241 } 242 243 /** 244 * Handle the --jmxurl option. 245 * @param token - option token to handle 246 * @param tokens - succeeding command arguments 247 * @throws Exception 248 */ 249 protected void handleOption(String token, List<String> tokens) throws Exception { 250 // Try to handle the options first 251 if (token.equals("--jmxurl")) { 252 // If no jmx url specified, or next token is a new option 253 if (tokens.isEmpty() || ((String)tokens.get(0)).startsWith("-")) { 254 context.printException(new IllegalArgumentException("JMX URL not specified.")); 255 } 256 257 // If jmx url already specified 258 if (getJmxServiceUrl() != null) { 259 context.printException(new IllegalArgumentException("Multiple JMX URL cannot be specified.")); 260 tokens.clear(); 261 } 262 263 String strJmxUrl = (String)tokens.remove(0); 264 try { 265 this.setJmxServiceUrl(new JMXServiceURL(strJmxUrl)); 266 } catch (MalformedURLException e) { 267 context.printException(e); 268 tokens.clear(); 269 } 270 } else if(token.equals("--pid")) { 271 if (isSunJVM()) { 272 if (tokens.isEmpty() || ((String) tokens.get(0)).startsWith("-")) { 273 context.printException(new IllegalArgumentException("pid not specified")); 274 return; 275 } 276 int pid = Integer.parseInt(tokens.remove(0)); 277 context.print("Connecting to pid: " + pid); 278 279 String jmxUrl = ConnectorAddressLink.importFrom(pid); 280 // If jmx url already specified 281 if (getJmxServiceUrl() != null) { 282 context.printException(new IllegalArgumentException("JMX URL already specified.")); 283 tokens.clear(); 284 } 285 try { 286 this.setJmxServiceUrl(new JMXServiceURL(jmxUrl)); 287 } catch (MalformedURLException e) { 288 context.printException(e); 289 tokens.clear(); 290 } 291 } else { 292 context.printInfo("--pid option is not available for this VM, using default JMX url"); 293 } 294 } else if (token.equals("--jmxuser")) { 295 // If no jmx user specified, or next token is a new option 296 if (tokens.isEmpty() || ((String)tokens.get(0)).startsWith("-")) { 297 context.printException(new IllegalArgumentException("JMX user not specified.")); 298 } 299 this.setJmxUser((String) tokens.remove(0)); 300 } else if (token.equals("--jmxpassword")) { 301 // If no jmx password specified, or next token is a new option 302 if (tokens.isEmpty() || ((String)tokens.get(0)).startsWith("-")) { 303 context.printException(new IllegalArgumentException("JMX password not specified.")); 304 } 305 this.setJmxPassword((String) tokens.remove(0)); 306 } else if (token.equals("--jmxlocal")) { 307 this.setJmxUseLocal(true); 308 } else { 309 // Let the super class handle the option 310 super.handleOption(token, tokens); 311 } 312 } 313 314 public void execute(List<String> tokens) throws Exception { 315 try { 316 super.execute(tokens); 317 } finally { 318 closeJmxConnection(); 319 } 320 } 321 }