1 /*
2  * Copyright (c) 2004, 2008, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  *
25  */
26 
27 /*
28  * This file has been modified by jvmtop project authors
29  */
30 package com.jvmtop.openjdk.tools;
31 
32 import static java.lang.management.ManagementFactory.*;
33 
34 import java.beans.PropertyChangeEvent;
35 import java.beans.PropertyChangeListener;
36 import java.io.IOException;
37 import java.lang.management.ClassLoadingMXBean;
38 import java.lang.management.CompilationMXBean;
39 import java.lang.management.GarbageCollectorMXBean;
40 import java.lang.management.ManagementFactory;
41 import java.lang.management.MemoryMXBean;
42 import java.lang.management.OperatingSystemMXBean;
43 import java.lang.management.RuntimeMXBean;
44 import java.lang.management.ThreadMXBean;
45 import java.lang.ref.WeakReference;
46 import java.lang.reflect.InvocationHandler;
47 import java.lang.reflect.InvocationTargetException;
48 import java.lang.reflect.Method;
49 import java.lang.reflect.Proxy;
50 import java.rmi.NotBoundException;
51 import java.rmi.Remote;
52 import java.rmi.registry.LocateRegistry;
53 import java.rmi.registry.Registry;
54 import java.rmi.server.RMIClientSocketFactory;
55 import java.rmi.server.RemoteObject;
56 import java.rmi.server.RemoteObjectInvocationHandler;
57 import java.rmi.server.RemoteRef;
58 import java.util.ArrayList;
59 import java.util.Arrays;
60 import java.util.Collection;
61 import java.util.Collections;
62 import java.util.HashMap;
63 import java.util.Iterator;
64 import java.util.List;
65 import java.util.Map;
66 import java.util.Set;
67 import java.util.TreeSet;
68 
69 import javax.management.Attribute;
70 import javax.management.AttributeList;
71 import javax.management.AttributeNotFoundException;
72 import javax.management.InstanceNotFoundException;
73 import javax.management.IntrospectionException;
74 import javax.management.InvalidAttributeValueException;
75 import javax.management.MBeanException;
76 import javax.management.MBeanInfo;
77 import javax.management.MBeanOperationInfo;
78 import javax.management.MBeanServerConnection;
79 import javax.management.MalformedObjectNameException;
80 import javax.management.ObjectName;
81 import javax.management.ReflectionException;
82 import javax.management.remote.JMXConnector;
83 import javax.management.remote.JMXConnectorFactory;
84 import javax.management.remote.JMXServiceURL;
85 import javax.management.remote.rmi.RMIConnector;
86 import javax.management.remote.rmi.RMIServer;
87 import javax.rmi.ssl.SslRMIClientSocketFactory;
88 
89 import sun.rmi.server.UnicastRef2;
90 import sun.rmi.transport.LiveRef;
91 
92 public class ProxyClient
93 {
94 
95     private ConnectionState connectionState = ConnectionState.DISCONNECTED;
96 
97 
98     private static Map<String, ProxyClient> cache =
99         Collections.synchronizedMap(new HashMap<String, ProxyClient>());
100 
101     private volatile boolean isDead = true;
102     private String hostName = null;
103     private int port = 0;
104     private String userName = null;
105     private String password = null;
106     private boolean hasPlatformMXBeans = false;
107     private boolean hasHotSpotDiagnosticMXBean= false;
108     private boolean hasCompilationMXBean = false;
109     private boolean supportsLockUsage = false;
110 
111     // REVISIT: VMPanel and other places relying using getUrl().
112 
113     // set only if it's created for local monitoring
114     private LocalVirtualMachine lvm;
115 
116     // set only if it's created from a given URL via the Advanced tab
117     private String advancedUrl = null;
118 
119     private JMXServiceURL jmxUrl = null;
120     private MBeanServerConnection mbsc = null;
121     private SnapshotMBeanServerConnection server = null;
122     private JMXConnector jmxc = null;
123     private RMIServer stub = null;
124     private static final SslRMIClientSocketFactory sslRMIClientSocketFactory =
125             new SslRMIClientSocketFactory();
126     private String registryHostName = null;
127     private int registryPort = 0;
128     private boolean vmConnector = false;
129     private boolean sslRegistry = false;
130     private boolean sslStub = false;
131     final private String connectionName;
132     final private String displayName;
133 
134     private ClassLoadingMXBean    classLoadingMBean = null;
135     private CompilationMXBean     compilationMBean = null;
136     private MemoryMXBean          memoryMBean = null;
137     private OperatingSystemMXBean operatingSystemMBean = null;
138     private RuntimeMXBean         runtimeMBean = null;
139     private ThreadMXBean          threadMBean = null;
140 
141   private java.lang.management.OperatingSystemMXBean sunOperatingSystemMXBean       = null;
142 
143   //    private HotSpotDiagnosticMXBean                  hotspotDiagnosticMXBean = null;
144 
145     private List<GarbageCollectorMXBean>    garbageCollectorMBeans = null;
146 
147     final static private String HOTSPOT_DIAGNOSTIC_MXBEAN_NAME =
148         "com.sun.management:type=HotSpotDiagnostic";
149 
ProxyClient(String hostName, int port, String userName, String password)150     private ProxyClient(String hostName, int port,
151                         String userName, String password) throws IOException {
152         this.connectionName = getConnectionName(hostName, port, userName);
153         this.displayName = connectionName;
154         if (hostName.equals("localhost") && port == 0) {
155             // Monitor self
156             this.hostName = hostName;
157             this.port = port;
158         } else {
159             // Create an RMI connector client and connect it to
160             // the RMI connector server
161             final String urlPath = "/jndi/rmi://" + hostName + ":" + port +
162                                    "/jmxrmi";
163             JMXServiceURL url = new JMXServiceURL("rmi", "", 0, urlPath);
164             setParameters(url, userName, password);
165             vmConnector = true;
166             registryHostName = hostName;
167             registryPort = port;
168             checkSslConfig();
169         }
170     }
171 
ProxyClient(String url, String userName, String password)172     private ProxyClient(String url,
173                         String userName, String password) throws IOException {
174         this.advancedUrl = url;
175         this.connectionName = getConnectionName(url, userName);
176         this.displayName = connectionName;
177         setParameters(new JMXServiceURL(url), userName, password);
178     }
179 
ProxyClient(LocalVirtualMachine lvm)180     private ProxyClient(LocalVirtualMachine lvm) throws IOException {
181         this.lvm = lvm;
182         this.connectionName = getConnectionName(lvm);
183         this.displayName = "pid: " + lvm.vmid() + " " + lvm.displayName();
184     }
185 
setParameters(JMXServiceURL url, String userName, String password)186     private void setParameters(JMXServiceURL url, String userName, String password) {
187         this.jmxUrl = url;
188         this.hostName = jmxUrl.getHost();
189         this.port = jmxUrl.getPort();
190         this.userName = userName;
191         this.password = password;
192     }
193 
checkStub(Remote stub, Class<? extends Remote> stubClass)194     private static void checkStub(Remote stub,
195                                   Class<? extends Remote> stubClass) {
196         // Check remote stub is from the expected class.
197         //
198         if (stub.getClass() != stubClass) {
199             if (!Proxy.isProxyClass(stub.getClass())) {
200                 throw new SecurityException(
201                     "Expecting a " + stubClass.getName() + " stub!");
202             } else {
203                 InvocationHandler handler = Proxy.getInvocationHandler(stub);
204                 if (handler.getClass() != RemoteObjectInvocationHandler.class) {
205                     throw new SecurityException(
206                         "Expecting a dynamic proxy instance with a " +
207                         RemoteObjectInvocationHandler.class.getName() +
208                         " invocation handler!");
209                 } else {
210                     stub = (Remote) handler;
211                 }
212             }
213         }
214         // Check RemoteRef in stub is from the expected class
215         // "sun.rmi.server.UnicastRef2".
216         //
217         RemoteRef ref = ((RemoteObject)stub).getRef();
218         if (ref.getClass() != UnicastRef2.class) {
219             throw new SecurityException(
220                 "Expecting a " + UnicastRef2.class.getName() +
221                 " remote reference in stub!");
222         }
223         // Check RMIClientSocketFactory in stub is from the expected class
224         // "javax.rmi.ssl.SslRMIClientSocketFactory".
225         //
226         LiveRef liveRef = ((UnicastRef2)ref).getLiveRef();
227         RMIClientSocketFactory csf = liveRef.getClientSocketFactory();
228         if (csf == null || csf.getClass() != SslRMIClientSocketFactory.class) {
229             throw new SecurityException(
230                 "Expecting a " + SslRMIClientSocketFactory.class.getName() +
231                 " RMI client socket factory in stub!");
232         }
233     }
234 
235     private static final String rmiServerImplStubClassName =
236         "javax.management.remote.rmi.RMIServerImpl_Stub";
237     private static final Class<? extends Remote> rmiServerImplStubClass;
238 
239     static {
240         // FIXME: RMIServerImpl_Stub is generated at build time
241         // after jconsole is built.  We need to investigate if
242         // the Makefile can be fixed to build jconsole in the
243         // right order.  As a workaround for now, we dynamically
244         // load RMIServerImpl_Stub class instead of statically
245         // referencing it.
246         Class<? extends Remote> serverStubClass = null;
247         try {
248             serverStubClass = Class.forName(rmiServerImplStubClassName).asSubclass(Remote.class);
249         } catch (ClassNotFoundException e) {
250             // should never reach here
251             throw (InternalError) new InternalError(e.getMessage()).initCause(e);
252         }
253         rmiServerImplStubClass = serverStubClass;
254     }
255 
checkSslConfig()256     private void checkSslConfig() throws IOException {
257         // Get the reference to the RMI Registry and lookup RMIServer stub
258         //
259         Registry registry;
260         try {
261             registry =
262                 LocateRegistry.getRegistry(registryHostName, registryPort,
263                                            sslRMIClientSocketFactory);
264             try {
265                 stub = (RMIServer) registry.lookup("jmxrmi");
266             } catch (NotBoundException nbe) {
267                 throw (IOException)
268                     new IOException(nbe.getMessage()).initCause(nbe);
269             }
270             sslRegistry = true;
271         } catch (IOException e) {
272             registry =
273                 LocateRegistry.getRegistry(registryHostName, registryPort);
274             try {
275                 stub = (RMIServer) registry.lookup("jmxrmi");
276             } catch (NotBoundException nbe) {
277                 throw (IOException)
278                     new IOException(nbe.getMessage()).initCause(nbe);
279             }
280             sslRegistry = false;
281         }
282         // Perform the checks for secure stub
283         //
284         try {
285             checkStub(stub, rmiServerImplStubClass);
286             sslStub = true;
287         } catch (SecurityException e) {
288             sslStub = false;
289         }
290     }
291 
292     /**
293      * Returns true if the underlying RMI registry is SSL-protected.
294      *
295      * @exception UnsupportedOperationException If this {@code ProxyClient}
296      * does not denote a JMX connector for a JMX VM agent.
297      */
isSslRmiRegistry()298     public boolean isSslRmiRegistry() {
299         // Check for VM connector
300         //
301         if (!isVmConnector()) {
302             throw new UnsupportedOperationException(
303                 "ProxyClient.isSslRmiRegistry() is only supported if this " +
304                 "ProxyClient is a JMX connector for a JMX VM agent");
305         }
306         return sslRegistry;
307     }
308 
309     /**
310      * Returns true if the retrieved RMI stub is SSL-protected.
311      *
312      * @exception UnsupportedOperationException If this {@code ProxyClient}
313      * does not denote a JMX connector for a JMX VM agent.
314      */
isSslRmiStub()315     public boolean isSslRmiStub() {
316         // Check for VM connector
317         //
318         if (!isVmConnector()) {
319             throw new UnsupportedOperationException(
320                 "ProxyClient.isSslRmiStub() is only supported if this " +
321                 "ProxyClient is a JMX connector for a JMX VM agent");
322         }
323         return sslStub;
324     }
325 
326     /**
327      * Returns true if this {@code ProxyClient} denotes
328      * a JMX connector for a JMX VM agent.
329      */
isVmConnector()330     public boolean isVmConnector() {
331         return vmConnector;
332     }
333 
setConnectionState(ConnectionState state)334     private void setConnectionState(ConnectionState state) {
335         ConnectionState oldState = this.connectionState;
336         this.connectionState = state;
337     }
338 
getConnectionState()339     public ConnectionState getConnectionState() {
340         return this.connectionState;
341     }
342 
flush()343   public void flush()
344   {
345         if (server != null) {
346             server.flush();
347         }
348     }
349 
connect()350   public void connect() throws Exception
351   {
352         setConnectionState(ConnectionState.CONNECTING);
353         try {
354             tryConnect();
355             setConnectionState(ConnectionState.CONNECTED);
356         } catch (Exception e) {
357             setConnectionState(ConnectionState.DISCONNECTED);
358       throw e;
359         }
360     }
361 
tryConnect()362     private void tryConnect() throws IOException {
363         if (jmxUrl == null && "localhost".equals(hostName) && port == 0) {
364             // Monitor self
365             this.jmxc = null;
366             this.mbsc = ManagementFactory.getPlatformMBeanServer();
367             this.server = Snapshot.newSnapshot(mbsc);
368         } else {
369             // Monitor another process
370             if (lvm != null) {
371                 if (!lvm.isManageable()) {
372                     lvm.startManagementAgent();
373                     if (!lvm.isManageable()) {
374                         // FIXME: what to throw
375                         throw new IOException(lvm + "not manageable");
376                     }
377                 }
378                 if (this.jmxUrl == null) {
379                     this.jmxUrl = new JMXServiceURL(lvm.connectorAddress());
380                 }
381             }
382             // Need to pass in credentials ?
383             if (userName == null && password == null) {
384                 if (isVmConnector()) {
385                     // Check for SSL config on reconnection only
386                     if (stub == null) {
387                         checkSslConfig();
388                     }
389                     this.jmxc = new RMIConnector(stub, null);
390                     jmxc.connect();
391                 } else {
392                     this.jmxc = JMXConnectorFactory.connect(jmxUrl);
393                 }
394             } else {
395                 Map<String, String[]> env = new HashMap<String, String[]>();
396                 env.put(JMXConnector.CREDENTIALS,
397                         new String[] {userName, password});
398                 if (isVmConnector()) {
399                     // Check for SSL config on reconnection only
400                     if (stub == null) {
401                         checkSslConfig();
402                     }
403                     this.jmxc = new RMIConnector(stub, null);
404                     jmxc.connect(env);
405                 } else {
406                     this.jmxc = JMXConnectorFactory.connect(jmxUrl, env);
407                 }
408             }
409             this.mbsc = jmxc.getMBeanServerConnection();
410             this.server = Snapshot.newSnapshot(mbsc);
411         }
412         this.isDead = false;
413 
414         try {
415             ObjectName on = new ObjectName(THREAD_MXBEAN_NAME);
416             this.hasPlatformMXBeans = server.isRegistered(on);
417             this.hasHotSpotDiagnosticMXBean =
418                 server.isRegistered(new ObjectName(HOTSPOT_DIAGNOSTIC_MXBEAN_NAME));
419             // check if it has 6.0 new APIs
420             if (this.hasPlatformMXBeans) {
421                 MBeanOperationInfo[] mopis = server.getMBeanInfo(on).getOperations();
422                 // look for findDeadlockedThreads operations;
423                 for (MBeanOperationInfo op : mopis) {
424                     if (op.getName().equals("findDeadlockedThreads")) {
425                         this.supportsLockUsage = true;
426                         break;
427                     }
428                 }
429 
430                 on = new ObjectName(COMPILATION_MXBEAN_NAME);
431                 this.hasCompilationMXBean = server.isRegistered(on);
432             }
433         } catch (MalformedObjectNameException e) {
434             // should not reach here
435             throw new InternalError(e.getMessage());
436         } catch (IntrospectionException e) {
437             InternalError ie = new InternalError(e.getMessage());
438             ie.initCause(e);
439             throw ie;
440         } catch (InstanceNotFoundException e) {
441             InternalError ie = new InternalError(e.getMessage());
442             ie.initCause(e);
443             throw ie;
444         } catch (ReflectionException e) {
445             InternalError ie = new InternalError(e.getMessage());
446             ie.initCause(e);
447             throw ie;
448         }
449 
450         if (hasPlatformMXBeans) {
451             // WORKAROUND for bug 5056632
452             // Check if the access role is correct by getting a RuntimeMXBean
453             getRuntimeMXBean();
454         }
455     }
456 
457     /**
458      * Gets a proxy client for a given local virtual machine.
459      */
getProxyClient(LocalVirtualMachine lvm)460     public static ProxyClient getProxyClient(LocalVirtualMachine lvm)
461         throws IOException {
462         final String key = getCacheKey(lvm);
463         ProxyClient proxyClient = cache.get(key);
464         if (proxyClient == null) {
465             proxyClient = new ProxyClient(lvm);
466             cache.put(key, proxyClient);
467         }
468         return proxyClient;
469     }
470 
getConnectionName(LocalVirtualMachine lvm)471     public static String getConnectionName(LocalVirtualMachine lvm) {
472         return Integer.toString(lvm.vmid());
473     }
474 
getCacheKey(LocalVirtualMachine lvm)475     private static String getCacheKey(LocalVirtualMachine lvm) {
476         return Integer.toString(lvm.vmid());
477     }
478 
479     /**
480      * Gets a proxy client for a given JMXServiceURL.
481      */
getProxyClient(String url, String userName, String password)482     public static ProxyClient getProxyClient(String url,
483                                              String userName, String password)
484         throws IOException {
485         final String key = getCacheKey(url, userName, password);
486         ProxyClient proxyClient = cache.get(key);
487         if (proxyClient == null) {
488             proxyClient = new ProxyClient(url, userName, password);
489             cache.put(key, proxyClient);
490         }
491         return proxyClient;
492     }
493 
getConnectionName(String url, String userName)494     public static String getConnectionName(String url,
495                                            String userName) {
496         if (userName != null && userName.length() > 0) {
497             return userName + "@" + url;
498         } else {
499             return url;
500         }
501     }
502 
getCacheKey(String url, String userName, String password)503     private static String getCacheKey(String url,
504                                       String userName, String password) {
505         return (url == null ? "" : url) + ":" +
506                (userName == null ? "" : userName) + ":" +
507                (password == null ? "" : password);
508     }
509 
510     /**
511      * Gets a proxy client for a given "hostname:port".
512      */
getProxyClient(String hostName, int port, String userName, String password)513     public static ProxyClient getProxyClient(String hostName, int port,
514                                              String userName, String password)
515         throws IOException {
516         final String key = getCacheKey(hostName, port, userName, password);
517         ProxyClient proxyClient = cache.get(key);
518         if (proxyClient == null) {
519             proxyClient = new ProxyClient(hostName, port, userName, password);
520             cache.put(key, proxyClient);
521         }
522         return proxyClient;
523     }
524 
getConnectionName(String hostName, int port, String userName)525     public static String getConnectionName(String hostName, int port,
526                                            String userName) {
527         String name = hostName + ":" + port;
528         if (userName != null && userName.length() > 0) {
529             return userName + "@" + name;
530         } else {
531             return name;
532         }
533     }
534 
getCacheKey(String hostName, int port, String userName, String password)535     private static String getCacheKey(String hostName, int port,
536                                       String userName, String password) {
537         return (hostName == null ? "" : hostName) + ":" +
538                port + ":" +
539                (userName == null ? "" : userName) + ":" +
540                (password == null ? "" : password);
541     }
542 
connectionName()543     public String connectionName() {
544         return connectionName;
545     }
546 
getDisplayName()547     public String getDisplayName() {
548         return displayName;
549     }
550 
551     @Override
toString()552     public String toString() {
553             return displayName;
554     }
555 
getMBeanServerConnection()556    public MBeanServerConnection getMBeanServerConnection() {
557        return mbsc;
558    }
559 
getSnapshotMBeanServerConnection()560     public SnapshotMBeanServerConnection getSnapshotMBeanServerConnection() {
561         return server;
562     }
563 
getUrl()564     public String getUrl() {
565         return advancedUrl;
566     }
567 
getHostName()568     public String getHostName() {
569         return hostName;
570     }
571 
getPort()572     public int getPort() {
573         return port;
574     }
575 
getVmid()576     public int getVmid() {
577         return (lvm != null) ? lvm.vmid() : 0;
578     }
579 
getUserName()580     public String getUserName() {
581         return userName;
582     }
583 
getPassword()584     public String getPassword() {
585         return password;
586     }
587 
disconnect()588     public void disconnect() {
589         // Reset remote stub
590         stub = null;
591         // Close MBeanServer connection
592         if (jmxc != null) {
593             try {
594                 jmxc.close();
595             } catch (IOException e) {
596                 // Ignore ???
597             }
598         }
599         // Reset platform MBean references
600         classLoadingMBean = null;
601         compilationMBean = null;
602         memoryMBean = null;
603         operatingSystemMBean = null;
604         runtimeMBean = null;
605         threadMBean = null;
606         sunOperatingSystemMXBean = null;
607         garbageCollectorMBeans = null;
608         // Set connection state to DISCONNECTED
609         if (!isDead) {
610             isDead = true;
611             setConnectionState(ConnectionState.DISCONNECTED);
612         }
613     }
614 
615     /**
616      * Returns the list of domains in which any MBean is
617      * currently registered.
618      */
getDomains()619     public String[] getDomains() throws IOException {
620         return server.getDomains();
621     }
622 
623     /**
624      * Returns a map of MBeans with ObjectName as the key and MBeanInfo value
625      * of a given domain.  If domain is <tt>null</tt>, all MBeans
626      * are returned.  If no MBean found, an empty map is returned.
627      *
628      */
getMBeans(String domain)629     public Map<ObjectName, MBeanInfo> getMBeans(String domain)
630         throws IOException {
631 
632         ObjectName name = null;
633         if (domain != null) {
634             try {
635                 name = new ObjectName(domain + ":*");
636             } catch (MalformedObjectNameException e) {
637                 // should not reach here
638                 assert(false);
639             }
640         }
641         Set mbeans = server.queryNames(name, null);
642         Map<ObjectName,MBeanInfo> result =
643             new HashMap<ObjectName,MBeanInfo>(mbeans.size());
644         Iterator iterator = mbeans.iterator();
645         while (iterator.hasNext()) {
646             Object object = iterator.next();
647             if (object instanceof ObjectName) {
648                 ObjectName o = (ObjectName)object;
649                 try {
650                     MBeanInfo info = server.getMBeanInfo(o);
651                     result.put(o, info);
652                 } catch (IntrospectionException e) {
653                     // TODO: should log the error
654                 } catch (InstanceNotFoundException e) {
655                     // TODO: should log the error
656                 } catch (ReflectionException e) {
657                     // TODO: should log the error
658                 }
659             }
660         }
661         return result;
662     }
663 
664     /**
665      * Returns a list of attributes of a named MBean.
666      *
667      */
getAttributes(ObjectName name, String[] attributes)668     public AttributeList getAttributes(ObjectName name, String[] attributes)
669         throws IOException {
670         AttributeList list = null;
671         try {
672             list = server.getAttributes(name, attributes);
673         } catch (InstanceNotFoundException e) {
674             // TODO: A MBean may have been unregistered.
675             // need to set up listener to listen for MBeanServerNotification.
676         } catch (ReflectionException e) {
677             // TODO: should log the error
678         }
679         return list;
680     }
681 
682     /**
683      * Set the value of a specific attribute of a named MBean.
684      */
setAttribute(ObjectName name, Attribute attribute)685     public void setAttribute(ObjectName name, Attribute attribute)
686         throws InvalidAttributeValueException,
687                MBeanException,
688                IOException {
689         try {
690             server.setAttribute(name, attribute);
691         } catch (InstanceNotFoundException e) {
692             // TODO: A MBean may have been unregistered.
693         } catch (AttributeNotFoundException e) {
694             assert(false);
695         } catch (ReflectionException e) {
696             // TODO: should log the error
697         }
698     }
699 
700     /**
701      * Invokes an operation of a named MBean.
702      *
703      * @throws MBeanException Wraps an exception thrown by
704      *      the MBean's invoked method.
705      */
invoke(ObjectName name, String operationName, Object[] params, String[] signature)706     public Object invoke(ObjectName name, String operationName,
707                          Object[] params, String[] signature)
708         throws IOException, MBeanException {
709         Object result = null;
710         try {
711             result = server.invoke(name, operationName, params, signature);
712         } catch (InstanceNotFoundException e) {
713             // TODO: A MBean may have been unregistered.
714         } catch (ReflectionException e) {
715             // TODO: should log the error
716         }
717         return result;
718     }
719 
getClassLoadingMXBean()720     public synchronized ClassLoadingMXBean getClassLoadingMXBean() throws IOException {
721         if (hasPlatformMXBeans && classLoadingMBean == null) {
722             classLoadingMBean =
723                 newPlatformMXBeanProxy(server, CLASS_LOADING_MXBEAN_NAME,
724                                        ClassLoadingMXBean.class);
725         }
726         return classLoadingMBean;
727     }
728 
getCompilationMXBean()729     public synchronized CompilationMXBean getCompilationMXBean() throws IOException {
730         if (hasCompilationMXBean && compilationMBean == null) {
731             compilationMBean =
732                 newPlatformMXBeanProxy(server, COMPILATION_MXBEAN_NAME,
733                                        CompilationMXBean.class);
734         }
735         return compilationMBean;
736     }
737 
738 
getGarbageCollectorMXBeans()739     public synchronized Collection<GarbageCollectorMXBean> getGarbageCollectorMXBeans()
740         throws IOException {
741 
742         // TODO: How to deal with changes to the list??
743         if (garbageCollectorMBeans == null) {
744             ObjectName gcName = null;
745             try {
746                 gcName = new ObjectName(GARBAGE_COLLECTOR_MXBEAN_DOMAIN_TYPE + ",*");
747             } catch (MalformedObjectNameException e) {
748                 // should not reach here
749                 assert(false);
750             }
751             Set mbeans = server.queryNames(gcName, null);
752             if (mbeans != null) {
753                 garbageCollectorMBeans = new ArrayList<GarbageCollectorMXBean>();
754                 Iterator iterator = mbeans.iterator();
755                 while (iterator.hasNext()) {
756                     ObjectName on = (ObjectName) iterator.next();
757                     String name = GARBAGE_COLLECTOR_MXBEAN_DOMAIN_TYPE +
758                         ",name=" + on.getKeyProperty("name");
759 
760                     GarbageCollectorMXBean mBean =
761                         newPlatformMXBeanProxy(server, name,
762                                                GarbageCollectorMXBean.class);
763                         garbageCollectorMBeans.add(mBean);
764                 }
765             }
766         }
767         return garbageCollectorMBeans;
768     }
769 
getMemoryMXBean()770     public synchronized MemoryMXBean getMemoryMXBean() throws IOException {
771         if (hasPlatformMXBeans && memoryMBean == null) {
772             memoryMBean =
773                 newPlatformMXBeanProxy(server, MEMORY_MXBEAN_NAME,
774                                        MemoryMXBean.class);
775         }
776         return memoryMBean;
777     }
778 
getRuntimeMXBean()779     public synchronized RuntimeMXBean getRuntimeMXBean() throws IOException {
780         if (hasPlatformMXBeans && runtimeMBean == null) {
781             runtimeMBean =
782                 newPlatformMXBeanProxy(server, RUNTIME_MXBEAN_NAME,
783                                        RuntimeMXBean.class);
784         }
785         return runtimeMBean;
786     }
787 
788 
getThreadMXBean()789     public synchronized ThreadMXBean getThreadMXBean() throws IOException {
790         if (hasPlatformMXBeans && threadMBean == null) {
791             threadMBean =
792                 newPlatformMXBeanProxy(server, THREAD_MXBEAN_NAME,
793                                        ThreadMXBean.class);
794         }
795         return threadMBean;
796     }
797 
getOperatingSystemMXBean()798     public synchronized OperatingSystemMXBean getOperatingSystemMXBean() throws IOException {
799         if (hasPlatformMXBeans && operatingSystemMBean == null) {
800             operatingSystemMBean =
801                 newPlatformMXBeanProxy(server, OPERATING_SYSTEM_MXBEAN_NAME,
802                                        OperatingSystemMXBean.class);
803         }
804         return operatingSystemMBean;
805     }
806 
807   public synchronized java.lang.management.OperatingSystemMXBean
getSunOperatingSystemMXBean()808         getSunOperatingSystemMXBean() throws IOException {
809 
810         try {
811             ObjectName on = new ObjectName(OPERATING_SYSTEM_MXBEAN_NAME);
812             if (sunOperatingSystemMXBean == null) {
813                 if (server.isInstanceOf(on,
814             "java.lang.management.OperatingSystemMXBean"))
815         {
816                     sunOperatingSystemMXBean =
817                         newPlatformMXBeanProxy(server,
818                             OPERATING_SYSTEM_MXBEAN_NAME,
819               //     com.sun.management.OperatingSystemMXBean.class);
820               java.lang.management.OperatingSystemMXBean.class);
821                 }
822             }
823         } catch (InstanceNotFoundException e) {
824              return null;
825         } catch (MalformedObjectNameException e) {
826              return null; // should never reach here
827         }
828         return sunOperatingSystemMXBean;
829     }
830 
831   /*
832       public synchronized HotSpotDiagnosticMXBean getHotSpotDiagnosticMXBean() throws IOException {
833           if (hasHotSpotDiagnosticMXBean && hotspotDiagnosticMXBean == null) {
834               hotspotDiagnosticMXBean =
835                   newPlatformMXBeanProxy(server, HOTSPOT_DIAGNOSTIC_MXBEAN_NAME,
836                                          HotSpotDiagnosticMXBean.class);
837           }
838           return hotspotDiagnosticMXBean;
839       }
840       */
841 
getMXBean(ObjectName objName, Class<T> interfaceClass)842     public <T> T getMXBean(ObjectName objName, Class<T> interfaceClass)
843         throws IOException {
844         return newPlatformMXBeanProxy(server,
845                                       objName.toString(),
846                                       interfaceClass);
847 
848     }
849 
850     // Return thread IDs of deadlocked threads or null if any.
851     // It finds deadlocks involving only monitors if it's a Tiger VM.
852     // Otherwise, it finds deadlocks involving both monitors and
853     // the concurrent locks.
findDeadlockedThreads()854     public long[] findDeadlockedThreads() throws IOException {
855         ThreadMXBean tm = getThreadMXBean();
856         if (supportsLockUsage && tm.isSynchronizerUsageSupported()) {
857             return tm.findDeadlockedThreads();
858         } else {
859             return tm.findMonitorDeadlockedThreads();
860         }
861     }
862 
markAsDead()863     public synchronized void markAsDead() {
864         disconnect();
865     }
866 
isDead()867     public boolean isDead() {
868         return isDead;
869     }
870 
isConnected()871     boolean isConnected() {
872         return !isDead();
873     }
874 
hasPlatformMXBeans()875     boolean hasPlatformMXBeans() {
876         return this.hasPlatformMXBeans;
877     }
878 
hasHotSpotDiagnosticMXBean()879     boolean hasHotSpotDiagnosticMXBean() {
880         return this.hasHotSpotDiagnosticMXBean;
881     }
882 
isLockUsageSupported()883     boolean isLockUsageSupported() {
884         return supportsLockUsage;
885     }
886 
isRegistered(ObjectName name)887     public boolean isRegistered(ObjectName name) throws IOException {
888         return server.isRegistered(name);
889     }
890 
addPropertyChangeListener(PropertyChangeListener listener)891   public void addPropertyChangeListener(PropertyChangeListener listener)
892   {
893 
894   }
895 
addWeakPropertyChangeListener(PropertyChangeListener listener)896   public void addWeakPropertyChangeListener(PropertyChangeListener listener)
897   {
898     if (!(listener instanceof WeakPCL))
899     {
900       listener = new WeakPCL(listener);
901     }
902 
903   }
904 
removePropertyChangeListener(PropertyChangeListener listener)905   public void removePropertyChangeListener(PropertyChangeListener listener)
906   {
907 
908   }
909 
910     /**
911      * The PropertyChangeListener is handled via a WeakReference
912      * so as not to pin down the listener.
913      */
914     private class WeakPCL extends WeakReference<PropertyChangeListener>
915                           implements PropertyChangeListener {
WeakPCL(PropertyChangeListener referent)916         WeakPCL(PropertyChangeListener referent) {
917             super(referent);
918         }
919 
propertyChange(PropertyChangeEvent pce)920         public void propertyChange(PropertyChangeEvent pce) {
921             PropertyChangeListener pcl = get();
922 
923             if (pcl == null) {
924                 // The referent listener was GC'ed, we're no longer
925                 // interested in PropertyChanges, remove the listener.
926                 dispose();
927             } else {
928                 pcl.propertyChange(pce);
929             }
930         }
931 
dispose()932         private void dispose() {
933             removePropertyChangeListener(this);
934         }
935     }
936 
937     //
938     // Snapshot MBeanServerConnection:
939     //
940     // This is an object that wraps an existing MBeanServerConnection and adds
941     // caching to it, as follows:
942     //
943     // - The first time an attribute is called in a given MBean, the result is
944     //   cached. Every subsequent time getAttribute is called for that attribute
945     //   the cached result is returned.
946     //
947     // - Before every call to VMPanel.update() or when the Refresh button in the
948     //   Attributes table is pressed down the attributes cache is flushed. Then
949     //   any subsequent call to getAttribute will retrieve all the values for
950     //   the attributes that are known to the cache.
951     //
952     // - The attributes cache uses a learning approach and only the attributes
953     //   that are in the cache will be retrieved between two subsequent updates.
954     //
955 
956     public interface SnapshotMBeanServerConnection
957             extends MBeanServerConnection {
958         /**
959          * Flush all cached values of attributes.
960          */
flush()961         public void flush();
962     }
963 
964     public static class Snapshot {
Snapshot()965         private Snapshot() {
966         }
967         public static SnapshotMBeanServerConnection
newSnapshot(MBeanServerConnection mbsc)968                 newSnapshot(MBeanServerConnection mbsc) {
969             final InvocationHandler ih = new SnapshotInvocationHandler(mbsc);
970             return (SnapshotMBeanServerConnection) Proxy.newProxyInstance(
971                     Snapshot.class.getClassLoader(),
972                     new Class[] {SnapshotMBeanServerConnection.class},
973                     ih);
974         }
975     }
976 
977     static class SnapshotInvocationHandler implements InvocationHandler {
978 
979         private final MBeanServerConnection conn;
980         private Map<ObjectName, NameValueMap> cachedValues = newMap();
981         private Map<ObjectName, Set<String>> cachedNames = newMap();
982 
983         @SuppressWarnings("serial")
984         private static final class NameValueMap
985                 extends HashMap<String, Object> {}
986 
SnapshotInvocationHandler(MBeanServerConnection conn)987         SnapshotInvocationHandler(MBeanServerConnection conn) {
988             this.conn = conn;
989         }
990 
flush()991         synchronized void flush() {
992             cachedValues = newMap();
993         }
994 
invoke(Object proxy, Method method, Object[] args)995         public Object invoke(Object proxy, Method method, Object[] args)
996                 throws Throwable {
997             final String methodName = method.getName();
998             if (methodName.equals("getAttribute")) {
999                 return getAttribute((ObjectName) args[0], (String) args[1]);
1000             } else if (methodName.equals("getAttributes")) {
1001                 return getAttributes((ObjectName) args[0], (String[]) args[1]);
1002             } else if (methodName.equals("flush")) {
1003                 flush();
1004                 return null;
1005             } else {
1006                 try {
1007                     return method.invoke(conn, args);
1008                 } catch (InvocationTargetException e) {
1009                     throw e.getCause();
1010                 }
1011             }
1012         }
1013 
getAttribute(ObjectName objName, String attrName)1014         private Object getAttribute(ObjectName objName, String attrName)
1015                 throws MBeanException, InstanceNotFoundException,
1016                 AttributeNotFoundException, ReflectionException, IOException {
1017             final NameValueMap values = getCachedAttributes(
1018                     objName, Collections.singleton(attrName));
1019             Object value = values.get(attrName);
1020             if (value != null || values.containsKey(attrName)) {
1021                 return value;
1022             }
1023             // Not in cache, presumably because it was omitted from the
1024             // getAttributes result because of an exception.  Following
1025             // call will probably provoke the same exception.
1026             return conn.getAttribute(objName, attrName);
1027         }
1028 
getAttributes( ObjectName objName, String[] attrNames)1029         private AttributeList getAttributes(
1030                 ObjectName objName, String[] attrNames) throws
1031                 InstanceNotFoundException, ReflectionException, IOException {
1032             final NameValueMap values = getCachedAttributes(
1033                     objName,
1034                     new TreeSet<String>(Arrays.asList(attrNames)));
1035             final AttributeList list = new AttributeList();
1036             for (String attrName : attrNames) {
1037                 final Object value = values.get(attrName);
1038                 if (value != null || values.containsKey(attrName)) {
1039                     list.add(new Attribute(attrName, value));
1040                 }
1041             }
1042             return list;
1043         }
1044 
getCachedAttributes( ObjectName objName, Set<String> attrNames)1045         private synchronized NameValueMap getCachedAttributes(
1046                 ObjectName objName, Set<String> attrNames) throws
1047                 InstanceNotFoundException, ReflectionException, IOException {
1048             NameValueMap values = cachedValues.get(objName);
1049             if (values != null && values.keySet().containsAll(attrNames)) {
1050                 return values;
1051             }
1052             attrNames = new TreeSet<String>(attrNames);
1053             Set<String> oldNames = cachedNames.get(objName);
1054             if (oldNames != null) {
1055                 attrNames.addAll(oldNames);
1056             }
1057             values = new NameValueMap();
1058             final AttributeList attrs = conn.getAttributes(
1059                     objName,
1060                     attrNames.toArray(new String[attrNames.size()]));
1061             for (Attribute attr : attrs.asList()) {
1062                 values.put(attr.getName(), attr.getValue());
1063             }
1064             cachedValues.put(objName, values);
1065             cachedNames.put(objName, attrNames);
1066             return values;
1067         }
1068 
1069         // See http://www.artima.com/weblogs/viewpost.jsp?thread=79394
newMap()1070         private static <K, V> Map<K, V> newMap() {
1071             return new HashMap<K, V>();
1072         }
1073     }
1074 
1075   /**
1076    * @return
1077    */
getProcessCpuTime()1078   public long getProcessCpuTime() throws Exception
1079   {
1080     try
1081     {
1082       String osMXBeanClassName = "com.sun.management.OperatingSystemMXBean";
1083       if (lvm.isJ9Mode())
1084       {
1085         osMXBeanClassName = "com.ibm.lang.management.OperatingSystemMXBean";
1086       }
1087 
1088       if (Proxy.isProxyClass(getOperatingSystemMXBean().getClass()))
1089       {
1090         Long cpuTime = (Long) Proxy
1091             .getInvocationHandler(getOperatingSystemMXBean())
1092             .invoke(
1093                 getOperatingSystemMXBean(),
1094                 Class.forName(osMXBeanClassName).getMethod("getProcessCpuTime"),
1095                 null);
1096 
1097         if (lvm.isJ9Mode())
1098         {
1099           //this is very strange, J9 does return the value in "100ns units"
1100           //which violates the management spec
1101           //see http://publib.boulder.ibm.com/infocenter/javasdk/v6r0/index.jsp?topic=%2Fcom.ibm.java.api.60.doc%2Fcom.ibm.lang.management%2Fcom%2Fibm%2Flang%2Fmanagement%2FOperatingSystemMXBean.html
1102           return cpuTime * 100;
1103         }
1104         else
1105         {
1106           return cpuTime;
1107         }
1108 
1109       }
1110       else
1111       {
1112         throw new UnsupportedOperationException(
1113             "Unsupported JDK, please report bug");
1114       }
1115     }
1116     catch (Throwable e)
1117     {
1118       throw new RuntimeException(e);
1119     }
1120   }
1121 }
1122