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