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 }