JMXRemoteNamespace.java 32.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
/*
 * Copyright 2008 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */

package javax.management.namespace;

import com.sun.jmx.defaults.JmxProperties;
import com.sun.jmx.mbeanserver.Util;
import com.sun.jmx.remote.util.EnvHelp;

import java.io.IOException;
33
import java.util.Collections;
34 35
import java.util.HashMap;
import java.util.Map;
36
import java.util.concurrent.atomic.AtomicLong;
37 38 39 40 41
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.management.AttributeChangeNotification;

42
import javax.management.ClientContext;
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
import javax.management.InstanceNotFoundException;
import javax.management.ListenerNotFoundException;
import javax.management.MBeanNotificationInfo;
import javax.management.MBeanServerConnection;
import javax.management.Notification;
import javax.management.NotificationBroadcasterSupport;
import javax.management.NotificationEmitter;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;
import javax.management.ObjectName;
import javax.management.event.EventClient;
import javax.management.remote.JMXConnectionNotification;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;

/**
 * A {@link JMXNamespace} that will connect to a remote MBeanServer
 * by creating a {@link javax.management.remote.JMXConnector} from a
 * {@link javax.management.remote.JMXServiceURL}.
 * <p>
 * You can call {@link #connect() connect()} and {@link #close close()}
 * several times. This MBean will emit an {@link AttributeChangeNotification}
 * when the value of its {@link #isConnected Connected} attribute changes.
 * </p>
 * <p>
 * The JMX Remote Namespace MBean is not connected until {@link
 * #connect() connect()} is explicitly called. The usual sequence of code to
 * create a JMX Remote Namespace is thus:
 * </p>
 * <pre>
 *     final String namespace = "mynamespace";
 *     final ObjectName name = {@link JMXNamespaces#getNamespaceObjectName
 *       JMXNamespaces.getNamespaceObjectName(namespace)};
 *     final JMXServiceURL remoteServerURL = .... ;
 *     final Map<String,Object> optionsMap = .... ;
 *     final MBeanServer masterMBeanServer = .... ;
 *     final JMXRemoteNamespace namespaceMBean = {@link #newJMXRemoteNamespace
 *        JMXRemoteNamespace.newJMXRemoteNamespace(remoteServerURL, optionsMap)};
 *     masterMBeanServer.registerMBean(namespaceMBean, name);
 *     namespaceMBean.connect();
 *     // or: masterMBeanServer.invoke(name, {@link #connect() "connect"}, null, null);
 * </pre>
 * <p>
 * The JMX Remote Namespace MBean will register for {@linkplain
 * JMXConnectionNotification JMX Connection Notifications} with its underlying
 * {@link JMXConnector}. When a JMX Connection Notification indicates that
 * the underlying connection has failed, the JMX Remote Namespace MBean
 * closes its underlying connector and switches its {@link #isConnected
 * Connected} attribute to false, emitting an {@link
 * AttributeChangeNotification}.
 * </p>
 * <p>
 * At this point, a managing application (or an administrator connected
 * through a management console) can attempt to reconnect the
 * JMX Remote Namespace MBean by calling its {@link #connect() connect()} method
 * again.
 * </p>
 * <p>Note that when the connection with the remote namespace fails, or when
 *    {@link #close} is called, then any notification subscription to
 *    MBeans registered in that namespace will be lost - unless a custom
 *    {@linkplain javax.management.event event service} supporting connection-less
 *    mode was used.
 * </p>
 * @since 1.7
 */
public class JMXRemoteNamespace
        extends JMXNamespace
        implements JMXRemoteNamespaceMBean, NotificationEmitter {

    /**
     * A logger for this class.
     */
    private static final Logger LOG = JmxProperties.NAMESPACE_LOGGER;


    // This connection listener is used to listen for connection events from
    // the underlying JMXConnector. It is used in particular to maintain the
    // "connected" state in this MBean.
    //
123 124
    private class ConnectionListener implements NotificationListener {
        private ConnectionListener() {
125 126 127 128 129 130 131
        }
        public void handleNotification(Notification notification,
                Object handback) {
            if (!(notification instanceof JMXConnectionNotification))
                return;
            final JMXConnectionNotification cn =
                    (JMXConnectionNotification)notification;
132 133 134 135 136
            final String type = cn.getType();
            if (JMXConnectionNotification.CLOSED.equals(type)
                    || JMXConnectionNotification.FAILED.equals(type)) {
                checkState(this,cn,(JMXConnector)handback);
            }
137 138 139 140 141 142 143 144 145 146 147 148 149
        }
    }

    // When the JMXRemoteNamespace is originally created, it is not connected,
    // which means that the source MBeanServer should be one that throws
    // exceptions for most methods.  When it is subsequently connected,
    // the methods should be forwarded to the MBeanServerConnection.
    // We handle this using MBeanServerConnectionWrapper.  The
    // MBeanServerConnection that is supplied to the constructor of
    // MBeanServerConnectionWrapper is ignored (and in fact it is null)
    // because the one that is actually used is the one supplied by the
    // override of getMBeanServerConnection().
    private static class JMXRemoteNamespaceDelegate
150
            extends MBeanServerConnectionWrapper {
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
        private volatile JMXRemoteNamespace parent=null;

        JMXRemoteNamespaceDelegate() {
            super(null,null);
        }
        @Override
        public MBeanServerConnection getMBeanServerConnection() {
            return parent.getMBeanServerConnection();
        }
        @Override
        public ClassLoader getDefaultClassLoader() {
            return parent.getDefaultClassLoader();
        }

        // Because this class is instantiated in the super() call from the
        // constructor of JMXRemoteNamespace, it cannot be an inner class.
        // This method achieves the effect that an inner class would have
        // had, of giving the class a reference to the outer "this".
        synchronized void initParentOnce(JMXRemoteNamespace parent) {
            if (this.parent != null)
                throw new UnsupportedOperationException("parent already set");
            this.parent=parent;

        }

    }

    private static final MBeanNotificationInfo connectNotification =
        new MBeanNotificationInfo(new String[] {
            AttributeChangeNotification.ATTRIBUTE_CHANGE},
            "Connected",
            "Emitted when the Connected state of this object changes");

184
    private static AtomicLong seqNumber = new AtomicLong(0);
185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223

    private final NotificationBroadcasterSupport broadcaster;
    private final ConnectionListener listener;
    private final JMXServiceURL jmxURL;
    private final Map<String,?> optionsMap;

    private volatile MBeanServerConnection server = null;
    private volatile JMXConnector conn = null;
    private volatile ClassLoader defaultClassLoader = null;

    /**
     * Creates a new instance of {@code JMXRemoteNamespace}.
     * <p>
     * This constructor is provided for subclasses.
     * To create a new instance of {@code JMXRemoteNamespace} call
     * {@link #newJMXRemoteNamespace
     *  JMXRemoteNamespace.newJMXRemoteNamespace(sourceURL, optionsMap)}.
     * </p>
     * @param sourceURL a JMX service URL that can be used to {@linkplain
     *        #connect() connect} to the
     *        source MBean Server. The source MBean Server is the remote
     *        MBean Server which contains the MBeans that will be mirrored
     *        in this namespace.
     * @param optionsMap the options map that will be passed to the
     *        {@link JMXConnectorFactory} when {@linkplain
     *        JMXConnectorFactory#newJMXConnector creating} the
     *        {@link JMXConnector} used to {@linkplain #connect() connect}
     *        to the remote source MBean Server.  Can be null, which is
     *        equivalent to an empty map.
     * @see #newJMXRemoteNamespace JMXRemoteNamespace.newJMXRemoteNamespace
     * @see #connect
     */
    protected JMXRemoteNamespace(JMXServiceURL sourceURL,
            Map<String,?> optionsMap) {
         super(new JMXRemoteNamespaceDelegate());
        ((JMXRemoteNamespaceDelegate)super.getSourceServer()).
                initParentOnce(this);

        // URL must not be null.
224 225 226
        if (sourceURL == null)
            throw new IllegalArgumentException("Null URL");
        this.jmxURL     = sourceURL;
227 228 229 230
        this.broadcaster =
            new NotificationBroadcasterSupport(connectNotification);

        // handles options
231
        this.optionsMap = unmodifiableMap(optionsMap);
232 233

        // handles (dis)connection events
234
        this.listener = new ConnectionListener();
235 236
    }

237 238 239 240 241 242 243
    // returns un unmodifiable view of a map.
    private static <K,V> Map<K,V> unmodifiableMap(Map<K,V> aMap) {
        if (aMap == null || aMap.isEmpty())
            return Collections.emptyMap();
        return Collections.unmodifiableMap(aMap);
    }

244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309
   /**
    * Returns the {@code JMXServiceURL} that is (or will be) used to
    * connect to the remote name space. <p>
    * @see #connect
    * @return The {@code JMXServiceURL} used to connect to the remote
    *         name space.
    */
    public JMXServiceURL getJMXServiceURL() {
        return jmxURL;
    }

    /**
    * In this class, this method never returns {@code null}, and the
    * address returned is the {@link  #getJMXServiceURL JMXServiceURL}
    * that is used by  this object to {@linkplain #connect} to the remote
    * name space. <p>
    * This behaviour might be overriden by subclasses, if needed.
    * For instance, a subclass might want to return {@code null} if it
    * doesn't want to expose that JMXServiceURL.
    */
    public JMXServiceURL getAddress() {
        return getJMXServiceURL();
    }

    private Map<String,?> getEnvMap() {
        return optionsMap;
    }

    public void addNotificationListener(NotificationListener listener,
            NotificationFilter filter, Object handback) {
        broadcaster.addNotificationListener(listener, filter, handback);
    }

    /**
     * A subclass that needs to send its own notifications must override
     * this method in order to return an {@link MBeanNotificationInfo
     * MBeanNotificationInfo[]} array containing both its own notification
     * infos and the notification infos of its super class. <p>
     * The implementation should probably look like:
     * <pre>
     *      final MBeanNotificationInfo[] myOwnNotifs = { .... };
     *      final MBeanNotificationInfo[] parentNotifs =
     *            super.getNotificationInfo();
     *      final Set<MBeanNotificationInfo> mergedResult =
     *            new HashSet<MBeanNotificationInfo>();
     *      mergedResult.addAll(Arrays.asList(myOwnNotifs));
     *      mergedResult.addAll(Arrays.asList(parentNotifs));
     *      return mergeResult.toArray(
     *             new MBeanNotificationInfo[mergedResult.size()]);
     * </pre>
     */
    public MBeanNotificationInfo[] getNotificationInfo() {
        return broadcaster.getNotificationInfo();
    }

    public void removeNotificationListener(NotificationListener listener)
    throws ListenerNotFoundException {
        broadcaster.removeNotificationListener(listener);
    }

    public void removeNotificationListener(NotificationListener listener,
            NotificationFilter filter, Object handback)
            throws ListenerNotFoundException {
        broadcaster.removeNotificationListener(listener, filter, handback);
    }

310 311
    private static long getNextSeqNumber() {
        return seqNumber.getAndIncrement();
312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359
    }


    /**
     * Sends a notification to registered listeners. Before the notification
     * is sent, the following steps are performed:
     * <ul><li>
     * If {@code n.getSequenceNumber() <= 0} set it to the next available
     * sequence number.</li>
     * <li>If {@code n.getSource() == null}, set it to the value returned by {@link
     * #getObjectName getObjectName()}.
     * </li></ul>
     * <p>This method can be called by subclasses in order to send their own
     *    notifications.
     *    In that case, these subclasses might also need to override
     *    {@link #getNotificationInfo} in order to declare their own
     *    {@linkplain MBeanNotificationInfo notification types}.
     * </p>
     * @param n The notification to send to registered listeners.
     * @see javax.management.NotificationBroadcasterSupport
     * @see #getNotificationInfo
     **/
    protected void sendNotification(Notification n) {
        if (n.getSequenceNumber()<=0)
            n.setSequenceNumber(getNextSeqNumber());
        if (n.getSource()==null)
            n.setSource(getObjectName());
        broadcaster.sendNotification(n);
    }

    private void checkState(ConnectionListener listener,
                            JMXConnectionNotification cn,
                            JMXConnector emittingConnector) {

        // Due to the asynchronous handling of notifications, it is
        // possible that this method is called for a JMXConnector
        // (or connection) which is already closed and replaced by a newer
        // one.
        //
        // This method attempts to determine the real state of the
        // connection - which might be different from what the notification
        // says.
        //
        // This is quite complex logic - because we try not to hold any
        // lock while evaluating the true value of the connected state,
        // while anyone might also call close() or connect() from a
        // different thread.
        // The method switchConnection() (called from here too) also has the
360
        // same kind of complex logic:
361 362 363 364
        //
        // We use the JMXConnector has a handback to the notification listener
        // (emittingConnector) in order to be able to determine whether the
        // notification concerns the current connector in use, or an older
365 366 367 368 369 370
        // one. The 'emittingConnector' is the connector from which the
        // notification originated. This could be an 'old' connector - as
        // closed() and connect() could already have been called before the
        // notification arrived. So what we do is to compare the
        // 'emittingConnector' with the current connector, to see if the
        // notification actually comes from the curent connector.
371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486
        //
        boolean remove = false;

        // whether the emittingConnector is already 'removed'
        synchronized (this) {
            if (this.conn != emittingConnector ||
                    JMXConnectionNotification.FAILED.equals(cn.getType()))
                remove = true;
        }

        // We need to unregister our listener from this 'removed' connector.
        // This is the only place where we remove the listener.
        //
        if (remove) {
            try {
                // This may fail if the connector is already closed.
                // But better unregister anyway...
                //
                emittingConnector.removeConnectionNotificationListener(
                        listener,null,
                        emittingConnector);
            } catch (Exception x) {
                LOG.log(Level.FINE,
                        "Failed to unregister connection listener"+x);
                LOG.log(Level.FINEST,
                        "Failed to unregister connection listener",x);
            }
            try {
                // This may fail if the connector is already closed.
                // But better call close twice and get an exception than
                // leaking...
                //
                emittingConnector.close();
            } catch (Exception x) {
                LOG.log(Level.FINEST,
                        "Failed to close old connector " +
                        "(failure was expected): "+x);
            }
        }

        // Now we checked whether our current connector is still alive.
        //
        boolean closed = false;
        final JMXConnector thisconn = this.conn;
        try {
            if (thisconn != null)
                thisconn.getConnectionId();
        } catch (IOException x) {
            LOG.finest("Connector already closed: "+x);
            closed = true;
        }

        // We got an IOException - the connector is not connected.
        // Need to forget it and switch our state to closed.
        //
        if (closed) {
            switchConnection(thisconn,null,null);
            try {
                // Usually this will fail... Better call close twice
                // and get an exception than leaking...
                //
                if (thisconn != emittingConnector || !remove)
                    thisconn.close();
            } catch (IOException x) {
                LOG.log(Level.FINEST,
                        "Failed to close connector (failure was expected): "
                        +x);
            }
        }
    }

    private final void switchConnection(JMXConnector oldc,
                                   JMXConnector newc,
                                   MBeanServerConnection mbs) {
        boolean connect = false;
        boolean close   = false;
        synchronized (this) {
            if (oldc != conn) {
                if (newc != null) {
                    try {
                        newc.close();
                    } catch (IOException x) {
                        LOG.log(Level.FINEST,
                                "Failed to close connector",x);
                    }
                }
                return;
            }
            if (conn == null && newc != null) connect=true;
            if (newc == null && conn != null) close = true;
            conn = newc;
            server = mbs;
        }
        if (connect || close) {
            boolean oldstate = close;
            boolean newstate = connect;
            final ObjectName myName = getObjectName();

            // In the uncommon case where the MBean is connected before
            // being registered, myName can be null...
            // If myName is null - we use 'this' as the source instead...
            //
            final Object source = (myName==null)?this:myName;
            final AttributeChangeNotification acn =
                    new AttributeChangeNotification(source,
                    getNextSeqNumber(),System.currentTimeMillis(),
                    String.valueOf(source)+
                    (newstate?" connected":" closed"),
                    "Connected",
                    "boolean",
                    Boolean.valueOf(oldstate),
                    Boolean.valueOf(newstate));
            sendNotification(acn);
        }
    }

487 488 489 490 491 492
    private void close(JMXConnector c) {
        try {
            if (c != null) c.close();
        } catch (Exception x) {
            // OK: we're gonna throw the original exception later.
            LOG.finest("Ignoring exception when closing connector: "+x);
493 494 495
        }
    }

496
    private JMXConnector connect(JMXServiceURL url, Map<String,?> env)
497
            throws IOException {
498
        final JMXConnector c = newJMXConnector(url, env);
499 500 501 502 503
        c.connect(env);
        return c;
    }

    /**
504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550
     * <p>Creates a new JMXConnector with the specified {@code url} and
     * {@code env} options map.  The default implementation of this method
     * returns {@link JMXConnectorFactory#newJMXConnector
     * JMXConnectorFactory.newJMXConnector(jmxURL, env)}.  Subclasses can
     * override this method to customize behavior.</p>
     *
     * @param url  The JMXServiceURL of the remote server.
     * @param optionsMap An options map that will be passed to the
     *        {@link JMXConnectorFactory} when {@linkplain
     *        JMXConnectorFactory#newJMXConnector creating} the
     *        {@link JMXConnector} that can connect to the remote source
     *        MBean Server.
     * @return A JMXConnector to use to connect to the remote server
     * @throws IOException if the connector could not be created.
     * @see JMXConnectorFactory#newJMXConnector(javax.management.remote.JMXServiceURL, java.util.Map)
     * @see #JMXRemoteNamespace
     */
    protected JMXConnector newJMXConnector(JMXServiceURL url,
            Map<String,?> optionsMap) throws IOException {
        return JMXConnectorFactory.newJMXConnector(jmxURL, optionsMap);
    }

    /**
     * <p>Called when a new connection is established using {@link #connect}
     * so that subclasses can customize the connection.  The default
     * implementation of this method effectively does the following:</p>
     *
     * <pre>
     * MBeanServerConnection mbsc = {@link JMXConnector#getMBeanServerConnection()
     *                               jmxc.getMBeanServerConnection()};
     * try {
     *     return {@link ClientContext#withDynamicContext
     *             ClientContext.withDynamicContext(mbsc)};
     * } catch (IllegalArgumentException e) {
     *     return mbsc;
     * }
     * </pre>
     *
     * <p>In other words, it arranges for the client context to be forwarded
     * to the remote MBean Server if the remote MBean Server supports contexts;
     * otherwise it ignores the client context.</p>
     *
     * <h4>Example: connecting to a remote namespace</h4>
     *
     * <p>A subclass that wanted to narrow into a namespace of
     * the remote MBeanServer might look like this:</p>
     *
551 552
     * <pre>
     * class JMXRemoteSubNamespace extends JMXRemoteNamespace {
553 554 555 556 557
     *     private final String subnamespace;
     *
     *     JMXRemoteSubNamespace(
     *             JMXServiceURL url, Map{@code <String, ?>} env, String subnamespace) {
     *        super(url, env);
558
     *        this.subnamespace = subnamespace;
559 560 561 562 563 564 565 566 567
     *     }
     *
     *     {@code @Override}
     *     protected MBeanServerConnection getMBeanServerConnection(
     *             JMXConnector jmxc) throws IOException {
     *         MBeanServerConnection mbsc = super.getMBeanServerConnection(jmxc);
     *         return {@link JMXNamespaces#narrowToNamespace(MBeanServerConnection,String)
     *                 JMXNamespaces.narrowToNamespace(mbsc, subnamespace)};
     *     }
568 569
     * }
     * </pre>
570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588
     *
     * <h4>Example: using the Event Service for notifications</h4>
     *
     * <p>Some connectors may have been designed to work with an earlier
     * version of the JMX API, and may not have been upgraded to use
     * the {@linkplain javax.management.event Event Service} defined in
     * this version of the JMX API.  In that case, and if the remote
     * server to which this JMXRemoteNamespace connects also contains
     * namespaces, it may be necessary to configure explicitly an {@linkplain
     * javax.management.event.EventClientDelegate#newForwarder Event Client
     * Forwarder} on the remote server side, and to force the use of an {@link
     * EventClient} on this client side.</p>
     *
     * <p>A subclass of {@link JMXRemoteNamespace} can provide an
     * implementation of {@code getMBeanServerConnection} that will force
     * notification subscriptions to flow through an {@link EventClient} over
     * a legacy protocol.  It can do so by overriding this method in the
     * following way:</p>
     *
589 590
     * <pre>
     * class JMXRemoteEventClientNamespace extends JMXRemoteNamespace {
591 592 593 594 595 596 597 598 599 600
     *     JMXRemoteEventClientNamespace(JMXServiceURL url, {@code Map<String,?>} env) {
     *         super(url, env);
     *     }
     *
     *     {@code @Override}
     *     protected MBeanServerConnection getMBeanServerConnection(JMXConnector jmxc)
     *             throws IOException {
     *         MBeanServerConnection mbsc = super.getMBeanServerConnection(jmxc);
     *         return EventClient.getEventClientConnection(mbsc);
     *     }
601 602
     * }
     * </pre>
603
     *
604 605
     * <p>
     * Note that the remote server also needs to provide an {@link
606 607 608 609
     * javax.management.event.EventClientDelegateMBean}: configuring only
     * the client side (this object) is not enough.</p>
     *
     * <p>In summary, this technique should be used if the remote server
610 611 612 613
     * supports JMX namespaces, but uses a JMX Connector Server whose
     * implementation does not transparently use the new Event Service
     * (as would be the case with the JMXMPConnectorServer implementation
     * from the reference implementation of the JMX Remote API 1.0
614 615 616 617 618 619 620 621 622 623 624 625
     * specification).</p>
     *
     * @param jmxc the newly-created {@code JMXConnector}.
     *
     * @return an {@code MBeanServerConnection} connected to the remote
     * MBeanServer.
     *
     * @throws IOException if the connection cannot be made.  If this method
     * throws {@code IOException} then the calling {@link #connect()} method
     * will also fail with an {@code IOException}.
     *
     * @see #connect
626
     */
627 628 629 630 631 632 633 634 635
    protected MBeanServerConnection getMBeanServerConnection(JMXConnector jmxc)
            throws IOException {
        final MBeanServerConnection mbsc = jmxc.getMBeanServerConnection();
        try {
            return ClientContext.withDynamicContext(mbsc);
        } catch (IllegalArgumentException e) {
            LOG.log(Level.FINER, "ClientContext.withDynamicContext", e);
            return mbsc;
        }
636 637
    }

638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660
    /**
     * {@inheritDoc}
     *
     * <p>The sequence of events when this method is called includes,
     * effectively, the following code:</p>
     *
     * <pre>
     * JMXServiceURL url = {@link #getJMXServiceURL getJMXServiceURL}();
     * JMXConnector jmxc = {@link #newJMXConnector newJMXConnector}(url, env);
     * jmxc.connect();
     * MBeanServerConnection mbsc = {@link #getMBeanServerConnection(JMXConnector)
     *                               getMBeanServerConnection}(jmxc);
     * </pre>
     *
     * <p>Here, {@code env} is a {@code Map} containing the entries from the
     * {@code optionsMap} that was passed to the {@linkplain #JMXRemoteNamespace
     * constructor} or to the {@link #newJMXRemoteNamespace newJMXRemoteNamespace}
     * factory method.</p>
     *
     * <p>Subclasses can customize connection behavior by overriding the
     * {@code getJMXServiceURL}, {@code newJMXConnector}, or
     * {@code getMBeanServerConnection} methods.</p>
     */
661 662 663 664 665 666 667
    public void connect() throws IOException {
        LOG.fine("connecting...");
        final Map<String,Object> env =
                new HashMap<String,Object>(getEnvMap());
        try {
            // XXX: We should probably document this...
            // This allows to specify a loader name - which will be
668
            // retrieved from the parent MBeanServer.
669 670 671 672 673 674 675 676 677 678 679 680 681
            defaultClassLoader =
                EnvHelp.resolveServerClassLoader(env,getMBeanServer());
        } catch (InstanceNotFoundException x) {
            final IOException io =
                    new IOException("ClassLoader not found");
            io.initCause(x);
            throw io;
        }
        env.put(JMXConnectorFactory.DEFAULT_CLASS_LOADER,defaultClassLoader);
        final JMXServiceURL url = getJMXServiceURL();
        final JMXConnector aconn = connect(url,env);
        final MBeanServerConnection msc;
        try {
682
            msc = getMBeanServerConnection(aconn);
683 684
            aconn.addConnectionNotificationListener(listener,null,aconn);
        } catch (IOException io) {
685
            close(aconn);
686 687
            throw io;
        } catch (RuntimeException x) {
688
            close(aconn);
689 690 691 692 693
            throw x;
        }

        switchConnection(conn,aconn,msc);

694
        LOG.fine("connected.");
695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809
    }

    public void close() throws IOException {
        if (conn == null) return;
        LOG.fine("closing...");
        // System.err.println(toString()+": closing...");
        conn.close();
        // System.err.println(toString()+": connector closed");
        switchConnection(conn,null,null);
        LOG.fine("closed.");
        // System.err.println(toString()+": closed");
    }

    MBeanServerConnection getMBeanServerConnection() {
        if (conn == null)
            throw newRuntimeIOException("getMBeanServerConnection: not connected");
        return server;
    }

    // Better than throwing UndeclaredThrowableException ...
    private RuntimeException newRuntimeIOException(String msg) {
        final IllegalStateException illegal = new IllegalStateException(msg);
        return Util.newRuntimeIOException(new IOException(msg,illegal));
    }

    /**
     * Returns the default class loader used by the underlying
     * {@link JMXConnector}.
     * @return the default class loader used when communicating with the
     *         remote source MBean server.
     **/
    ClassLoader getDefaultClassLoader() {
        if (conn == null)
            throw newRuntimeIOException("getMBeanServerConnection: not connected");
        return defaultClassLoader;
    }

    public boolean isConnected() {
        // This is a pleonasm
        return (conn != null) && (server != null);
    }


    /**
     * This name space handler will automatically {@link #close} its
     * connection with the remote source in {@code preDeregister}.
     **/
    @Override
    public void preDeregister() throws Exception {
        try {
            close();
        } catch (IOException x) {
            LOG.fine("Failed to close properly - exception ignored: " + x);
            LOG.log(Level.FINEST,
                    "Failed to close properly - exception ignored",x);
        }
        super.preDeregister();
    }

   /**
    * This method calls {@link
    * javax.management.MBeanServerConnection#getMBeanCount
    * getMBeanCount()} on the remote namespace.
    * @throws java.io.IOException if an {@link IOException} is raised when
    *         communicating with the remote source namespace.
    */
    @Override
    public Integer getMBeanCount() throws IOException {
        return getMBeanServerConnection().getMBeanCount();
    }

   /**
    * This method returns the result of calling {@link
    * javax.management.MBeanServerConnection#getDomains
    * getDomains()} on the remote namespace.
    * @throws java.io.IOException if an {@link IOException} is raised when
    *         communicating with the remote source namespace.
    */
    @Override
   public String[] getDomains() throws IOException {
       return getMBeanServerConnection().getDomains();
    }

   /**
    * This method returns the result of calling {@link
    * javax.management.MBeanServerConnection#getDefaultDomain
    * getDefaultDomain()} on the remote namespace.
    * @throws java.io.IOException if an {@link IOException} is raised when
    *         communicating with the remote source namespace.
    */
    @Override
    public String getDefaultDomain() throws IOException {
        return getMBeanServerConnection().getDefaultDomain();
    }

    /**
     * Creates a new instance of {@code JMXRemoteNamespace}.
     * @param sourceURL a JMX service URL that can be used to connect to the
     *        source MBean Server. The source MBean Server is the remote
     *        MBean Server which contains the MBeans that will be mirrored
     *        in this namespace.
     * @param optionsMap An options map that will be passed to the
     *        {@link JMXConnectorFactory} when {@linkplain
     *        JMXConnectorFactory#newJMXConnector creating} the
     *        {@link JMXConnector} used to connect to the remote source
     *        MBean Server.  Can be null, which is equivalent to an empty map.
     * @see #JMXRemoteNamespace JMXRemoteNamespace(sourceURL,optionsMap)
     * @see JMXConnectorFactory#newJMXConnector(javax.management.remote.JMXServiceURL, java.util.Map)
     */
     public static JMXRemoteNamespace newJMXRemoteNamespace(
             JMXServiceURL sourceURL,
             Map<String,?> optionsMap) {
         return new JMXRemoteNamespace(sourceURL, optionsMap);
     }
}