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    }