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