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
26package sun.tools.jconsole;
27
28import com.sun.management.HotSpotDiagnosticMXBean;
29import com.sun.tools.jconsole.JConsoleContext;
30import java.beans.PropertyChangeListener;
31import java.beans.PropertyChangeEvent;
32import java.io.IOException;
33import java.lang.management.*;
34import static java.lang.management.ManagementFactory.*;
35import java.lang.ref.WeakReference;
36import java.lang.reflect.*;
37import java.rmi.*;
38import java.rmi.registry.*;
39import java.rmi.server.*;
40import java.util.*;
41import javax.management.*;
42import javax.management.remote.*;
43import javax.management.remote.rmi.*;
44import javax.rmi.ssl.SslRMIClientSocketFactory;
45import javax.swing.event.SwingPropertyChangeSupport;
46import sun.rmi.server.UnicastRef2;
47import sun.rmi.transport.LiveRef;
48
49public 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
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
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
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
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
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
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     */
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     */
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     */
289    public boolean isVmConnector() {
290        return vmConnector;
291    }
292
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
300    public ConnectionState getConnectionState() {
301        return this.connectionState;
302    }
303
304    void flush() {
305        if (server != null) {
306            server.flush();
307        }
308    }
309
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
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     */
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
427    public static String getConnectionName(LocalVirtualMachine lvm) {
428        return Integer.toString(lvm.vmid());
429    }
430
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     */
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
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
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     */
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
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
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
499    public String connectionName() {
500        return connectionName;
501    }
502
503    public String getDisplayName() {
504        return displayName;
505    }
506
507    public String toString() {
508        if (!isConnected()) {
509            return Resources.format(Messages.CONNECTION_NAME__DISCONNECTED_, displayName);
510        } else {
511            return displayName;
512        }
513    }
514
515   public MBeanServerConnection getMBeanServerConnection() {
516       return mbsc;
517   }
518
519    public SnapshotMBeanServerConnection getSnapshotMBeanServerConnection() {
520        return server;
521    }
522
523    public String getUrl() {
524        return advancedUrl;
525    }
526
527    public String getHostName() {
528        return hostName;
529    }
530
531    public int getPort() {
532        return port;
533    }
534
535    public int getVmid() {
536        return (lvm != null) ? lvm.vmid() : 0;
537    }
538
539    public String getUserName() {
540        return userName;
541    }
542
543    public String getPassword() {
544        return password;
545    }
546
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     */
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     */
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     */
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     */
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     */
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
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
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
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
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
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
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
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
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
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
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
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.
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
843    public synchronized void markAsDead() {
844        disconnect();
845    }
846
847    public boolean isDead() {
848        return isDead;
849    }
850
851    boolean isConnected() {
852        return !isDead();
853    }
854
855    boolean hasPlatformMXBeans() {
856        return this.hasPlatformMXBeans;
857    }
858
859    boolean hasHotSpotDiagnosticMXBean() {
860        return this.hasHotSpotDiagnosticMXBean;
861    }
862
863    boolean isLockUsageSupported() {
864        return supportsLockUsage;
865    }
866
867    public boolean isRegistered(ObjectName name) throws IOException {
868        return server.isRegistered(name);
869    }
870
871    public void addPropertyChangeListener(PropertyChangeListener listener) {
872        propertyChangeSupport.addPropertyChangeListener(listener);
873    }
874
875    public void addWeakPropertyChangeListener(PropertyChangeListener listener) {
876        if (!(listener instanceof WeakPCL)) {
877            listener = new WeakPCL(listener);
878        }
879        propertyChangeSupport.addPropertyChangeListener(listener);
880    }
881
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 {
901        WeakPCL(PropertyChangeListener referent) {
902            super(referent);
903        }
904
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
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         */
946        public void flush();
947    }
948
949    public static class Snapshot {
950        private Snapshot() {
951        }
952        public static SnapshotMBeanServerConnection
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
972        SnapshotInvocationHandler(MBeanServerConnection conn) {
973            this.conn = conn;
974        }
975
976        synchronized void flush() {
977            cachedValues = newMap();
978        }
979
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
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
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
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
1055        private static <K, V> Map<K, V> newMap() {
1056            return new HashMap<K, V>();
1057        }
1058    }
1059}
1060