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