1 /*
   2  * Copyright (c) 2004, 2012, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.tools.jconsole;
  27 
  28 import com.sun.management.HotSpotDiagnosticMXBean;
  29 import com.sun.tools.jconsole.JConsoleContext;
  30 import java.beans.PropertyChangeListener;
  31 import java.beans.PropertyChangeEvent;
  32 import java.io.IOException;
  33 import java.lang.management.*;
  34 import static java.lang.management.ManagementFactory.*;
  35 import java.lang.ref.WeakReference;
  36 import java.lang.reflect.*;
  37 import java.rmi.*;
  38 import java.rmi.registry.*;
  39 import java.rmi.server.*;
  40 import java.util.*;
  41 import javax.management.*;
  42 import javax.management.remote.*;
  43 import javax.management.remote.rmi.*;
  44 import javax.rmi.ssl.SslRMIClientSocketFactory;
  45 import javax.swing.event.SwingPropertyChangeSupport;
  46 import sun.rmi.server.UnicastRef2;
  47 import sun.rmi.transport.LiveRef;
  48 
  49 public class ProxyClient implements JConsoleContext {
  50 
  51     private ConnectionState connectionState = ConnectionState.DISCONNECTED;
  52 
  53     // The SwingPropertyChangeSupport will fire events on the EDT
  54     private SwingPropertyChangeSupport propertyChangeSupport =
  55                                 new SwingPropertyChangeSupport(this, true);
  56 
  57     private static Map<String, ProxyClient> cache =
  58         Collections.synchronizedMap(new HashMap<String, ProxyClient>());
  59 
  60     private volatile boolean isDead = true;
  61     private String hostName = null;
  62     private int port = 0;
  63     private String userName = null;
  64     private String password = null;
  65     private boolean hasPlatformMXBeans = false;
  66     private boolean hasHotSpotDiagnosticMXBean= false;
  67     private boolean hasCompilationMXBean = false;
  68     private boolean supportsLockUsage = false;
  69 
  70     // REVISIT: VMPanel and other places relying using getUrl().
  71 
  72     // set only if it's created for local monitoring
  73     private LocalVirtualMachine lvm;
  74 
  75     // set only if it's created from a given URL via the Advanced tab
  76     private String advancedUrl = null;
  77 
  78     private JMXServiceURL jmxUrl = null;
  79     private MBeanServerConnection mbsc = null;
  80     private SnapshotMBeanServerConnection server = null;
  81     private JMXConnector jmxc = null;
  82     private RMIServer stub = null;
  83     private static final SslRMIClientSocketFactory sslRMIClientSocketFactory =
  84             new SslRMIClientSocketFactory();
  85     private String registryHostName = null;
  86     private int registryPort = 0;
  87     private boolean vmConnector = false;
  88     private boolean sslRegistry = false;
  89     private boolean sslStub = false;
  90     final private String connectionName;
  91     final private String displayName;
  92 
  93     private ClassLoadingMXBean    classLoadingMBean = null;
  94     private CompilationMXBean     compilationMBean = null;
  95     private MemoryMXBean          memoryMBean = null;
  96     private OperatingSystemMXBean operatingSystemMBean = null;
  97     private RuntimeMXBean         runtimeMBean = null;
  98     private ThreadMXBean          threadMBean = null;
  99 
 100     private com.sun.management.OperatingSystemMXBean sunOperatingSystemMXBean = null;
 101     private HotSpotDiagnosticMXBean                  hotspotDiagnosticMXBean = null;
 102 
 103     private List<MemoryPoolProxy>           memoryPoolProxies = null;
 104     private List<GarbageCollectorMXBean>    garbageCollectorMBeans = null;
 105 
 106     final static private String HOTSPOT_DIAGNOSTIC_MXBEAN_NAME =
 107         "com.sun.management:type=HotSpotDiagnostic";
 108 
 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 <tt>null</tt>, 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 = (ObjectName) 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 = (ObjectName) 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 }