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.Constructor;
023import java.lang.reflect.Method;
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.Set;
031
032import javax.management.MBeanServerConnection;
033import javax.management.remote.JMXConnector;
034import javax.management.remote.JMXConnectorFactory;
035import javax.management.remote.JMXServiceURL;
036
037import sun.management.ConnectorAddressLink;
038
039public 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}