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 }