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    }