1 /*
   2  * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
   3  * 
   4  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   5  *
   6  * The contents of this file are subject to the terms of either the Universal Permissive License
   7  * v 1.0 as shown at http://oss.oracle.com/licenses/upl
   8  *
   9  * or the following license:
  10  *
  11  * Redistribution and use in source and binary forms, with or without modification, are permitted
  12  * provided that the following conditions are met:
  13  * 
  14  * 1. Redistributions of source code must retain the above copyright notice, this list of conditions
  15  * and the following disclaimer.
  16  * 
  17  * 2. Redistributions in binary form must reproduce the above copyright notice, this list of
  18  * conditions and the following disclaimer in the documentation and/or other materials provided with
  19  * the distribution.
  20  * 
  21  * 3. Neither the name of the copyright holder nor the names of its contributors may be used to
  22  * endorse or promote products derived from this software without specific prior written permission.
  23  * 
  24  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
  25  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
  26  * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
  27  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  29  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  30  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
  31  * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  32  */
  33 package org.openjdk.jmc.rjmx.internal;
  34 
  35 import java.io.Closeable;
  36 import java.io.IOException;
  37 import java.lang.management.ManagementFactory;
  38 import java.net.MalformedURLException;
  39 import java.rmi.UnmarshalException;
  40 import java.util.Collection;
  41 import java.util.HashMap;
  42 import java.util.HashSet;
  43 import java.util.Iterator;
  44 import java.util.Map;
  45 import java.util.Set;
  46 import java.util.logging.Level;
  47 
  48 import javax.management.AttributeNotFoundException;
  49 import javax.management.InstanceNotFoundException;
  50 import javax.management.IntrospectionException;
  51 import javax.management.JMRuntimeException;
  52 import javax.management.MBeanException;
  53 import javax.management.MBeanInfo;
  54 import javax.management.MBeanServerConnection;
  55 import javax.management.MBeanServerDelegate;
  56 import javax.management.MBeanServerNotification;
  57 import javax.management.MalformedObjectNameException;
  58 import javax.management.Notification;
  59 import javax.management.NotificationListener;
  60 import javax.management.ObjectName;
  61 import javax.management.QueryExp;
  62 import javax.management.ReflectionException;
  63 import javax.management.remote.JMXConnectionNotification;
  64 import javax.management.remote.JMXConnector;
  65 import javax.management.remote.JMXConnectorFactory;
  66 import javax.management.remote.JMXServiceURL;
  67 import javax.rmi.ssl.SslRMIClientSocketFactory;
  68 
  69 import org.eclipse.core.runtime.ListenerList;
  70 import org.openjdk.jmc.common.version.JavaVersion;
  71 import org.openjdk.jmc.common.version.JavaVersionSupport;
  72 import org.openjdk.jmc.rjmx.ConnectionException;
  73 import org.openjdk.jmc.rjmx.ConnectionToolkit;
  74 import org.openjdk.jmc.rjmx.IConnectionDescriptor;
  75 import org.openjdk.jmc.rjmx.IServerDescriptor;
  76 import org.openjdk.jmc.rjmx.RJMXPlugin;
  77 import org.openjdk.jmc.rjmx.services.IOperation;
  78 import org.openjdk.jmc.rjmx.subscription.IMBeanHelperService;
  79 import org.openjdk.jmc.rjmx.subscription.IMBeanServerChangeListener;
  80 import org.openjdk.jmc.rjmx.subscription.IMRIService;
  81 import org.openjdk.jmc.rjmx.subscription.MRI;
  82 import org.openjdk.jmc.rjmx.subscription.internal.AttributeValueToolkit;
  83 import org.openjdk.jmc.rjmx.subscription.internal.InvoluntaryDisconnectException;
  84 import org.openjdk.jmc.rjmx.subscription.internal.MBeanMRIMetadataDB;
  85 import org.openjdk.jmc.ui.common.jvm.JVMDescriptor;
  86 
  87 /**
  88  * This class simplifies and hides some of the complexity of connecting to a JVM (supporting JSR-174
  89  * and JSR-160) using Remote JMX. The RJMXConnection is shared between several
  90  * {@link DefaultConnectionHandle}s, and when the last {@link DefaultConnectionHandle} using the
  91  * JRMXConnection is closed, the RJMXConnection will be automatically closed.
  92  */
  93 public class RJMXConnection implements Closeable, IMBeanHelperService {
  94 
  95         public final static String KEY_SOCKET_FACTORY = "com.sun.jndi.rmi.factory.socket"; //$NON-NLS-1$
  96 
  97         /**
  98          * The default port JMX
  99          */
 100         public static final int VALUE_DEFAULT_REMOTE_PORT_JMX = 7091;
 101 
 102         /**
 103          * Default recalibration interval. The server to client timediff is recalibrated every two
 104          * minutes per default.
 105          */
 106         private static final long VALUE_RECALIBRATION_INTERVAL = 120000;
 107         private static final long REMOTE_START_TIME_UNDEFINED = -1;
 108 
 109         // The ConnectionDescriptor used to create this RJMXConnection
 110         private final IConnectionDescriptor m_connectionDescriptor;
 111 
 112         private final IServerDescriptor m_serverDescriptor;
 113 
 114         // The MBean server connection used for all local and remote communication.
 115         private volatile MCMBeanServerConnection m_server;
 116 
 117         // The underlying JMX connection used when communicating remote.
 118         private JMXConnector m_jmxc;
 119 
 120         private final MBeanMRIMetadataDB m_mbeanDataProvider;
 121 
 122         // Variables used for calibrating the offset to the server clock.
 123         private long m_serverOffset;
 124         private long m_lastRecalibration;
 125         private long m_remoteStartTime = REMOTE_START_TIME_UNDEFINED;
 126 
 127         private boolean m_hasInitializedAllMBeans = false;
 128         private final HashMap<ObjectName, MBeanInfo> m_cachedInfos = new HashMap<>();
 129         private volatile Set<ObjectName> m_cachedMBeanNames = new HashSet<>();
 130         private final Runnable m_onFailCallback;
 131         private final ListenerList<IMBeanServerChangeListener> m_mbeanListeners = new ListenerList<>();
 132         private final NotificationListener m_registrationListener = new NotificationListener() {
 133                 @Override
 134                 public void handleNotification(Notification notification, Object handback) {
 135                         if (notification instanceof MBeanServerNotification) {
 136                                 ObjectName name = ((MBeanServerNotification) notification).getMBeanName();
 137                                 if (notification.getType().equals(MBeanServerNotification.REGISTRATION_NOTIFICATION)) {
 138                                         try {
 139                                                 synchronized (m_cachedInfos) {
 140                                                         getMBeanInfo(name);
 141                                                         if (m_cachedMBeanNames.size() > 0) {
 142                                                                 m_cachedMBeanNames.add(name);
 143                                                         }
 144                                                 }
 145                                                 for (IMBeanServerChangeListener l : m_mbeanListeners) {
 146                                                         l.mbeanRegistered(name);
 147                                                 }
 148                                         } catch (Exception e) {
 149                                                 RJMXPlugin.getDefault().getLogger().log(Level.WARNING,
 150                                                                 "Could not retrieve MBean information for " + name + '!', e); //$NON-NLS-1$
 151                                         }
 152                                 } else if (notification.getType().equals(MBeanServerNotification.UNREGISTRATION_NOTIFICATION)) {
 153                                         synchronized (m_cachedInfos) {
 154                                                 m_cachedInfos.remove(name);
 155                                                 m_cachedMBeanNames.remove(name);
 156                                         }
 157                                         for (IMBeanServerChangeListener l : m_mbeanListeners) {
 158                                                 l.mbeanUnregistered(name);
 159                                         }
 160                                 }
 161                         }
 162                 }
 163         };
 164 
 165         private final NotificationListener m_disconnectListener = new NotificationListener() {
 166 
 167                 @Override
 168                 public void handleNotification(Notification notification, Object handback) {
 169                         if (notification != null && (JMXConnectionNotification.CLOSED.equals(notification.getType())
 170                                         || JMXConnectionNotification.FAILED.equals(notification.getType()))) {
 171                                 close();
 172                                 if (m_onFailCallback != null) {
 173                                         m_onFailCallback.run();
 174                                 }
 175                         }
 176                 }
 177 
 178         };
 179 
 180         private final Object connectionStateLock = new Object();
 181 
 182         /**
 183          * Creates a new remote JMX connection to the specified host, using the supplied credentials. If
 184          * password is null or empty, it will be ignored. Will attempt to set up a connection to the
 185          * server immediately. The Constructor will fail if no connection could be established.
 186          *
 187          * @throws MalformedURLException
 188          */
 189         public RJMXConnection(IConnectionDescriptor connectionDescriptor, IServerDescriptor serverDescriptor,
 190                         Runnable onFailCallback) {
 191                 if (connectionDescriptor == null) {
 192                         throw new IllegalArgumentException("Connection descriptor must not be null!"); //$NON-NLS-1$
 193                 }
 194                 if (serverDescriptor == null) {
 195                         throw new IllegalArgumentException("Server descriptor must not be null!"); //$NON-NLS-1$
 196                 }
 197                 m_onFailCallback = onFailCallback;
 198                 m_connectionDescriptor = connectionDescriptor;
 199                 m_serverDescriptor = serverDescriptor;
 200                 m_mbeanDataProvider = new MBeanMRIMetadataDB(this);
 201                 addMBeanServerChangeListener(m_mbeanDataProvider);
 202         }
 203 
 204         public IServerDescriptor getServerDescriptor() {
 205                 return m_serverDescriptor;
 206         }
 207 
 208         public IConnectionDescriptor getConnectionDescriptor() {
 209                 return m_connectionDescriptor;
 210         }
 211 
 212         /**
 213          * Disconnects the connection from the RJMX server
 214          */
 215         @Override
 216         public void close() {
 217                 synchronized (connectionStateLock) {
 218                         if (isConnected()) {
 219                                 m_server.dispose();
 220                                 tryRemovingListener();
 221                                 clearCollections();
 222                                 m_server = null;
 223                                 if (m_jmxc != null) {
 224                                         try {
 225                                                 m_jmxc.close();
 226                                         } catch (Exception e) {
 227                                                 RJMXPlugin.getDefault().getLogger().log(Level.INFO, "Problem when closing connection.", e); //$NON-NLS-1$
 228                                         }
 229                                         m_jmxc = null;
 230                                 }
 231                         }
 232                 }
 233         }
 234 
 235         /**
 236          * Sometimes we can fail to remove the unregister listeners from the MBeanConnection, causing
 237          * JMX to keep a reference to this instance. To minimize impact if this happens, we clear all
 238          * collections from data.
 239          */
 240         private void clearCollections() {
 241                 clearCache();
 242         }
 243 
 244         private void tryRemovingListener() {
 245                 try {
 246                         ensureConnected().removeNotificationListener(MBeanServerDelegate.DELEGATE_NAME, m_registrationListener);
 247                 } catch (Exception e) {
 248                         RJMXPlugin.getDefault().getLogger().log(Level.WARNING,
 249                                         "Failed to remove unregistration listener! Lost connection?", e); //$NON-NLS-1$
 250                 }
 251         }
 252 
 253         /**
 254          * Returns whether the underlying connector is connected
 255          *
 256          * @return true if the underlying connector is still connected
 257          */
 258         public boolean isConnected() {
 259                 return m_server != null;
 260         }
 261 
 262         @Override
 263         public Set<ObjectName> getMBeanNames() throws IOException {
 264                 synchronized (m_cachedInfos) {
 265                         if (m_cachedMBeanNames.size() == 0) {
 266                                 MBeanServerConnection server = ensureConnected();
 267                                 m_cachedMBeanNames = server.queryNames(null, null);
 268                         }
 269                         return new HashSet<>(m_cachedMBeanNames);
 270                 }
 271         }
 272 
 273         /**
 274          * Returns the bean information for the MBeans matching the domain and query.
 275          *
 276          * @param domain
 277          *            the domain for which to retrieve the information.
 278          * @param query
 279          *            a query to filter for which MBeans to retrieve the information.
 280          * @return a map with the ObjectNames and their associated MBeanInfos.
 281          * @throws IOException
 282          *             if the connection failed or some other IO related problem occurred.
 283          * @throws MalformedObjectNameException
 284          *             if a particularly malign (malformatted) domain was specified.
 285          */
 286         private HashMap<ObjectName, MBeanInfo> getMBeanInfos(String domain, QueryExp query)
 287                         throws MalformedObjectNameException, IOException {
 288                 MBeanServerConnection server = ensureConnected();
 289                 ObjectName objectName = null;
 290                 int skippedMBeanCounter = 0;
 291                 if (domain != null) {
 292                         objectName = new ObjectName(domain + ":*"); //$NON-NLS-1$
 293                 }
 294                 Set<ObjectName> names = server.queryNames(objectName, query);
 295                 HashMap<ObjectName, MBeanInfo> infos = new HashMap<>(names.size());
 296 
 297                 Iterator<ObjectName> iter = names.iterator();
 298                 while (iter.hasNext()) {
 299                         ObjectName name = iter.next();
 300                         try {
 301                                 infos.put(name, getMBeanInfo(name));
 302                         } catch (NullPointerException e) {
 303                                 /*
 304                                  * Skip problematic MBeans when connecting. Workaround implemented so that we can
 305                                  * connect to JBoss 4.2.3.
 306                                  */
 307                                 RJMXPlugin.getDefault().getLogger().log(Level.WARNING, "Skipping " + name.toString() //$NON-NLS-1$
 308                                                 + ". Could not retrieve the MBean info for the MBean. Set log level to fine for stacktrace!"); //$NON-NLS-1$
 309                                 RJMXPlugin.getDefault().getLogger().log(Level.FINE, e.getMessage(), e);
 310                                 skippedMBeanCounter++;
 311                         } catch (UnmarshalException e) {
 312                                 RJMXPlugin.getDefault().getLogger().log(Level.WARNING, "Skipping " //$NON-NLS-1$
 313                                                 + name.toString()
 314                                                 + ". Could not retrieve the MBean info due to marshalling problems. Set log level to fine for stacktrace!"); //$NON-NLS-1$
 315                                 RJMXPlugin.getDefault().getLogger().log(Level.FINE, e.getMessage(), e);
 316                                 skippedMBeanCounter++;
 317                         } catch (InstanceNotFoundException e) {
 318                                 /*
 319                                  * We may end up here if the MBean was unregistered between the call to
 320                                  * getMBeanNames and getMBeanInfo(). Should not be very common though.
 321                                  */
 322                                 RJMXPlugin.getDefault().getLogger().log(Level.WARNING, "Skipping " + name.toString() //$NON-NLS-1$
 323                                                 + ". It could not be found and may have been unregistered very recently. Set log level to fine to fine for stacktrace!"); //$NON-NLS-1$
 324                                 RJMXPlugin.getDefault().getLogger().log(Level.FINE, e.getMessage(), e);
 325                         } catch (IntrospectionException e) {
 326                                 IOException exception = new IOException("Error accessing the bean."); //$NON-NLS-1$
 327                                 exception.initCause(e);
 328                                 throw exception;
 329                         } catch (ReflectionException e) {
 330                                 IOException exception = new IOException("Error accessing the bean."); //$NON-NLS-1$
 331                                 exception.initCause(e);
 332                                 throw exception;
 333                         }
 334                 }
 335                 if (skippedMBeanCounter > 0) {
 336                         RJMXPlugin.getDefault().getLogger().log(Level.WARNING,
 337                                         "Skipped " + skippedMBeanCounter + " MBeans because of marshalling related issues."); //$NON-NLS-1$ //$NON-NLS-2$
 338                 }
 339                 return infos;
 340         }
 341 
 342         /**
 343          * Tries to add a dedicated notification listener that removes unloaded MBeans.
 344          */
 345         private void tryToAddMBeanNotificationListener() {
 346                 try {
 347                         ensureConnected().addNotificationListener(MBeanServerDelegate.DELEGATE_NAME, m_registrationListener, null,
 348                                         null);
 349                 } catch (InstanceNotFoundException e) {
 350                         // Will typically not happen.
 351                 } catch (IOException e) {
 352                         // Will typically not happen.
 353                 }
 354         }
 355 
 356         /**
 357          * Tries to populate the MBean information cache if it is empty.
 358          *
 359          * @throws IOException
 360          *             if the connection failed or some other IO related problem occurred.
 361          */
 362         private void initializeMBeanInfos() throws IOException {
 363                 synchronized (m_cachedInfos) {
 364                         if (!m_hasInitializedAllMBeans) {
 365                                 try {
 366                                         getMBeanInfos(null, null);
 367                                         m_hasInitializedAllMBeans = true;
 368                                 } catch (MalformedObjectNameException e) {
 369                                         assert (false); // Should not be able to get here!
 370                                 }
 371                         }
 372                 }
 373         }
 374 
 375         @Override
 376         public HashMap<ObjectName, MBeanInfo> getMBeanInfos() throws IOException {
 377                 synchronized (m_cachedInfos) {
 378                         initializeMBeanInfos();
 379                         return new HashMap<>(m_cachedInfos);
 380                 }
 381         }
 382 
 383         @Override
 384         public MBeanInfo getMBeanInfo(ObjectName mbean)
 385                         throws InstanceNotFoundException, IntrospectionException, ReflectionException, IOException {
 386                 synchronized (m_cachedInfos) {
 387                         MBeanInfo mbeanInfo = m_cachedInfos.get(mbean);
 388                         if (mbeanInfo == null) {
 389                                 MBeanServerConnection server = ensureConnected();
 390                                 mbeanInfo = server.getMBeanInfo(mbean);
 391                                 if (mbeanInfo != null) {
 392                                         m_cachedInfos.put(mbean, mbeanInfo);
 393                                 }
 394                         }
 395                         return mbeanInfo;
 396                 }
 397         }
 398 
 399         @Override
 400         public Object getAttributeValue(MRI attribute) throws AttributeNotFoundException, MBeanException, IOException,
 401                         InstanceNotFoundException, ReflectionException {
 402                 try {
 403                         MBeanServerConnection server = ensureConnected();
 404                         return AttributeValueToolkit.getAttribute(server, attribute);
 405                 } catch (JMRuntimeException e) {
 406                         throw new MBeanException(e, e.getMessage());
 407                 }
 408         }
 409 
 410         public boolean connect() throws ConnectionException {
 411                 JVMDescriptor jvmInfo = getServerDescriptor().getJvmInfo();
 412                 if (jvmInfo != null && jvmInfo.getJavaVersion() != null
 413                                 && !new JavaVersion(jvmInfo.getJavaVersion()).isGreaterOrEqualThan(JavaVersionSupport.JDK_6)) {
 414                         throw new ConnectionException("Too low JDK Version. JDK 1.6 or higher is supported."); //$NON-NLS-1$
 415                 }
 416                 synchronized (connectionStateLock) {
 417                         if (isConnected()) {
 418                                 return false;
 419                         }
 420                         JMXServiceURL url;
 421                         try {
 422                                 url = m_connectionDescriptor.createJMXServiceURL();
 423                         } catch (IOException e1) {
 424                                 throw new WrappedConnectionException(m_serverDescriptor.getDisplayName(), null, e1);
 425                         }
 426 
 427                         try {
 428                                 // Use same convention as Sun. localhost:0 means "VM, monitor thyself!"
 429                                 String hostName = ConnectionToolkit.getHostName(url);
 430                                 if (hostName != null && (hostName.equals("localhost")) //$NON-NLS-1$
 431                                                 && ConnectionToolkit.getPort(url) == 0) {
 432                                         m_server = new MCMBeanServerConnection(ManagementFactory.getPlatformMBeanServer());
 433                                 } else {
 434                                         establishConnection(url, m_connectionDescriptor.getEnvironment());
 435                                 }
 436                                 tryToAddMBeanNotificationListener();
 437                                 m_remoteStartTime = fetchServerStartTime();
 438                                 return true;
 439                         } catch (Exception e) {
 440                                 m_server = null;
 441                                 throw new WrappedConnectionException(m_serverDescriptor.getDisplayName(), url, e);
 442                         }
 443                 }
 444         }
 445 
 446         private long fetchServerStartTime() throws IOException {
 447                 try {
 448                         return ConnectionToolkit.getRuntimeBean(ensureConnected()).getStartTime();
 449                 } catch (IllegalArgumentException e) {
 450                         RJMXPlugin.getDefault().getLogger().log(Level.WARNING,
 451                                         "Could not find the Runtime MBean. You are probably connecting to a custom MBean server. Functionality will be limited.", //$NON-NLS-1$
 452                                         e);
 453                         return REMOTE_START_TIME_UNDEFINED;
 454                 }
 455         }
 456 
 457         /**
 458          * Attempts to establish a connection. If the connection fails due to symptoms indicating the
 459          * registry using SSL, another attempt to connect will be performed, with the required additions
 460          * to the env.
 461          */
 462         private void establishConnection(JMXServiceURL serviceURL, Map<String, Object> env) throws IOException {
 463                 try {
 464                         connectJmxConnector(serviceURL, env);
 465                 } catch (IOException exception) {
 466                         try {
 467                                 if (env.get(KEY_SOCKET_FACTORY) instanceof SslRMIClientSocketFactory) {
 468                                         env.remove(KEY_SOCKET_FACTORY);
 469                                 } else {
 470                                         env.put(KEY_SOCKET_FACTORY, new SslRMIClientSocketFactory());
 471                                 }
 472                                 connectJmxConnector(serviceURL, env);
 473                         } catch (IOException ioe) {
 474                                 // So we failed even when changing to secure sockets. Original exception was probably spot on...
 475                                 throw exception;
 476                         }
 477                 }
 478                 m_server = new MCMBeanServerConnection(m_jmxc.getMBeanServerConnection());
 479         }
 480 
 481         private void connectJmxConnector(JMXServiceURL serviceURL, Map<String, Object> env) throws IOException {
 482                 m_jmxc = JMXConnectorFactory.newJMXConnector(serviceURL, env);
 483                 m_jmxc.addConnectionNotificationListener(m_disconnectListener, null, null);
 484                 // This is a hack to provide SSL properties to the RMI SSL server socket factory using system properties
 485                 JMXRMISystemPropertiesProvider.setup();
 486                 // According to javadocs, has to pass env here too (which mSA RMI took literally).
 487                 m_jmxc.connect(env);
 488         }
 489 
 490         @Override
 491         public long getApproximateServerTime(long localTime) {
 492                 long startTime = System.currentTimeMillis();
 493                 if ((startTime - m_lastRecalibration) > VALUE_RECALIBRATION_INTERVAL
 494                                 && m_remoteStartTime != REMOTE_START_TIME_UNDEFINED) {
 495                         try {
 496                                 /*
 497                                  * FIXME: JMC-4270 - Server time approximation is not reliable. Since JDK-6523160,
 498                                  * getUptime can no longer be used to derive the current server time. Find some
 499                                  * other way to do this.
 500                                  */
 501                                 long uptime = ConnectionToolkit.getRuntimeBean(ensureConnected()).getUptime();
 502                                 long returnTime = System.currentTimeMillis();
 503                                 long localTimeEstimate = (startTime + returnTime) / 2;
 504                                 m_serverOffset = m_remoteStartTime + uptime - localTimeEstimate;
 505                                 m_lastRecalibration = returnTime;
 506                         } catch (Exception e) {
 507                                 RJMXPlugin.getDefault().getLogger().log(Level.SEVERE, "Could not recalibrate server offset", e); //$NON-NLS-1$
 508                         }
 509                 }
 510                 return localTime + m_serverOffset;
 511         }
 512 
 513         /**
 514          * Returns the MBeanServerConnection. Yes, this breaks abstraction a bit, and should only be
 515          * used by the MBeanBrowser. Everybody else should be using subscriptions anyway.
 516          *
 517          * @return the MBeanServerConnection currently in use by this connection. May be null if none is
 518          *         currently in use.
 519          */
 520         MBeanServerConnection getMBeanServer() {
 521                 return m_server;
 522         }
 523 
 524         /**
 525          * Ok, so this method may not be very useful, from a strict synchronization perspective, but at
 526          * least it is now done in ONE place.
 527          *
 528          * @return a MBeanServerConnection, if connected (or at least non-null).
 529          * @throws IOException
 530          *             if not connected.
 531          */
 532         private MBeanServerConnection ensureConnected() throws IOException {
 533                 MBeanServerConnection server = m_server;
 534                 if (server == null) {
 535                         throw new InvoluntaryDisconnectException("Server is disconnected!"); //$NON-NLS-1$
 536                 }
 537                 return server;
 538         }
 539 
 540         public void clearCache() {
 541                 synchronized (m_cachedInfos) {
 542                         m_cachedInfos.clear();
 543                         m_cachedMBeanNames.clear();
 544                         m_hasInitializedAllMBeans = false;
 545                 }
 546         }
 547 
 548         @Override
 549         public String toString() {
 550                 return "RJMX Connection: " + m_serverDescriptor.getDisplayName(); //$NON-NLS-1$
 551         }
 552 
 553         @Override
 554         public void removeMBeanServerChangeListener(IMBeanServerChangeListener listener) {
 555                 m_mbeanListeners.remove(listener);
 556         }
 557 
 558         @Override
 559         public void addMBeanServerChangeListener(IMBeanServerChangeListener listener) {
 560                 m_mbeanListeners.add(listener);
 561         }
 562 
 563         @Override
 564         public Map<MRI, Map<String, Object>> getMBeanMetadata(ObjectName mbean) {
 565                 return m_mbeanDataProvider.getMBeanData(mbean);
 566         }
 567 
 568         /**
 569          * Returns the IOperations available for the specified MBean.
 570          *
 571          * @param mbean
 572          *            the MBean for which to return the information.
 573          * @return the operations that can be invoked on this mbean.
 574          * @throws Exception
 575          *             if the connection failed or some other problem occurred when trying create
 576          *             operations.
 577          */
 578         public Collection<IOperation> getOperations(ObjectName mbean) throws Exception {
 579                 MBeanServerConnection srv = ensureConnected();
 580                 return MBeanOperationWrapper.createOperations(mbean, srv.getMBeanInfo(mbean).getOperations(), srv);
 581         }
 582 
 583         IMRIService getMRIService() {
 584                 return m_mbeanDataProvider;
 585         }
 586 
 587 }