jdk/src/share/classes/javax/management/namespace/JMXRemoteNamespace.java
changeset 1156 bbc2d15aaf7a
child 1222 78e3d021d528
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/javax/management/namespace/JMXRemoteNamespace.java	Thu Sep 04 14:46:36 2008 +0200
@@ -0,0 +1,837 @@
+/*
+ * 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.namespace.JMXNamespaceUtils;
+import com.sun.jmx.namespace.NamespaceInterceptor.DynamicProbe;
+import com.sun.jmx.remote.util.EnvHelp;
+
+import java.io.IOException;
+import java.security.AccessControlException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.management.AttributeChangeNotification;
+
+import javax.management.InstanceNotFoundException;
+import javax.management.ListenerNotFoundException;
+import javax.management.MBeanNotificationInfo;
+import javax.management.MBeanPermission;
+import javax.management.MBeanServerConnection;
+import javax.management.MalformedObjectNameException;
+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;
+
+    private static final Logger PROBE_LOG = Logger.getLogger(
+            JmxProperties.NAMESPACE_LOGGER_NAME+".probe");
+
+
+    // 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.
+    //
+    private static class ConnectionListener implements NotificationListener {
+        private final JMXRemoteNamespace handler;
+        private ConnectionListener(JMXRemoteNamespace handler) {
+            this.handler = handler;
+        }
+        public void handleNotification(Notification notification,
+                Object handback) {
+            if (!(notification instanceof JMXConnectionNotification))
+                return;
+            final JMXConnectionNotification cn =
+                    (JMXConnectionNotification)notification;
+            handler.checkState(this,cn,(JMXConnector)handback);
+        }
+    }
+
+    // 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
+            extends MBeanServerConnectionWrapper
+            implements DynamicProbe {
+        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;
+
+        }
+
+        public boolean isProbeRequested() {
+            return this.parent.isProbeRequested();
+        }
+    }
+
+    private static final MBeanNotificationInfo connectNotification =
+        new MBeanNotificationInfo(new String[] {
+            AttributeChangeNotification.ATTRIBUTE_CHANGE},
+            "Connected",
+            "Emitted when the Connected state of this object changes");
+
+    private static long seqNumber=0;
+
+    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;
+    private volatile boolean probed;
+
+    /**
+     * 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.
+        this.jmxURL     = JMXNamespaceUtils.checkNonNull(sourceURL,"url");
+        this.broadcaster =
+            new NotificationBroadcasterSupport(connectNotification);
+
+        // handles options
+        this.optionsMap = JMXNamespaceUtils.unmodifiableMap(optionsMap);
+
+        // handles (dis)connection events
+        this.listener = new ConnectionListener(this);
+
+        // XXX TODO: remove the probe, or simplify it.
+        this.probed = false;
+    }
+
+   /**
+    * 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;
+    }
+
+    boolean isProbeRequested() {
+        return probed==false;
+    }
+
+    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);
+    }
+
+    private static synchronized long getNextSeqNumber() {
+        return seqNumber++;
+    }
+
+
+    /**
+     * 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
+        // same kind of complex logic.
+        //
+        // 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
+        // one.
+        //
+        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);
+        }
+    }
+
+    private void closeall(JMXConnector... a) {
+        for (JMXConnector c : a) {
+            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);
+            }
+        }
+    }
+
+    JMXConnector connect(JMXServiceURL url, Map<String,?> env)
+            throws IOException {
+        final JMXConnector c = newJMXConnector(jmxURL, env);
+        c.connect(env);
+        return c;
+    }
+
+    /**
+     * Creates a new JMXConnector with the specified {@code url} and
+     * {@code env} options map.
+     * <p>
+     * This method first calls {@link JMXConnectorFactory#newJMXConnector
+     * JMXConnectorFactory.newJMXConnector(jmxURL, env)} to obtain a new
+     * JMX connector, and returns that.
+     * </p>
+     * <p>
+     * A subclass of {@link JMXRemoteNamespace} can provide an implementation
+     * that connects to a  sub namespace of the remote server by subclassing
+     * this class in the following way:
+     * <pre>
+     * class JMXRemoteSubNamespace extends JMXRemoteNamespace {
+     *    private final String subnamespace;
+     *    JMXRemoteSubNamespace(JMXServiceURL url,
+     *              Map{@code <String,?>} env, String subnamespace) {
+     *        super(url,options);
+     *        this.subnamespace = subnamespace;
+     *    }
+     *    protected JMXConnector newJMXConnector(JMXServiceURL url,
+     *              Map<String,?> env) throws IOException {
+     *        final JMXConnector inner = super.newJMXConnector(url,env);
+     *        return {@link JMXNamespaces#narrowToNamespace(JMXConnector,String)
+     *               JMXNamespaces.narrowToNamespace(inner,subnamespace)};
+     *    }
+     * }
+     * </pre>
+     * </p>
+     * <p>
+     * Some connectors, like the JMXMP connector server defined by the
+     * version 1.2 of the JMX API may not have been upgraded to use the
+     * new {@linkplain javax.management.event Event Service} defined in this
+     * version of the JMX API.
+     * <p>
+     * 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.
+     * <br>
+     * A subclass of {@link JMXRemoteNamespace} can provide an implementation
+     * of {@code newJMXConnector} that will force notification subscriptions
+     * to flow through an {@link EventClient} over a legacy protocol by
+     * overriding this method in the following way:
+     * </p>
+     * <pre>
+     * class JMXRemoteEventClientNamespace extends JMXRemoteNamespace {
+     *    JMXRemoteSubNamespaceConnector(JMXServiceURL url,
+     *              Map<String,?> env) {
+     *        super(url,options);
+     *    }
+     *    protected JMXConnector newJMXConnector(JMXServiceURL url,
+     *              Map<String,?> env) throws IOException {
+     *        final JMXConnector inner = super.newJMXConnector(url,env);
+     *        return {@link EventClient#withEventClient(
+     *                JMXConnector) EventClient.withEventClient(inner)};
+     *    }
+     * }
+     * </pre>
+     * <p>
+     * Note that the remote server also needs to provide an {@link
+     * javax.management.event.EventClientDelegateMBean}: only configuring
+     * the client side (this object) is not enough.<br>
+     * In summary, this technique should be used if the remote server
+     * 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
+     * specification).
+     * </p>
+     * @param url  The JMXServiceURL of the remote server.
+     * @param optionsMap An unmodifiable 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 An unconnected JMXConnector to use to connect to the remote
+     *         server
+     * @throws java.io.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 {
+        final JMXConnector c =
+                JMXConnectorFactory.newJMXConnector(jmxURL, optionsMap);
+// TODO: uncomment this when contexts are added
+//        return ClientContext.withDynamicContext(c);
+        return c;
+    }
+
+    public void connect() throws IOException {
+        if (conn != null) {
+            try {
+               // This is much too fragile. It must go away!
+               PROBE_LOG.finest("Probing again...");
+               triggerProbe(getMBeanServerConnection());
+            } catch(Exception x) {
+                close();
+                Throwable cause = x;
+                // if the cause is a security exception - rethrows it...
+                while (cause != null) {
+                    if (cause instanceof SecurityException)
+                        throw (SecurityException) cause;
+                    cause = cause.getCause();
+                }
+                throw new IOException("connection failed: cycle?",x);
+            }
+        }
+        LOG.fine("connecting...");
+        // TODO remove these traces
+        // System.err.println(getInitParameter()+" 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
+            // retrieved from the paret MBeanServer.
+            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 {
+            msc = aconn.getMBeanServerConnection();
+            aconn.addConnectionNotificationListener(listener,null,aconn);
+        } catch (IOException io) {
+            closeall(aconn);
+            throw io;
+        } catch (RuntimeException x) {
+            closeall(aconn);
+            throw x;
+        }
+
+
+        // XXX Revisit here
+        // Note from the author: This business of switching connection is
+        // incredibly complex. Isn't there any means to simplify it?
+        //
+        switchConnection(conn,aconn,msc);
+        try {
+           triggerProbe(msc);
+        } catch(Exception x) {
+            close();
+            Throwable cause = x;
+            // if the cause is a security exception - rethrows it...
+            while (cause != null) {
+                if (cause instanceof SecurityException)
+                    throw (SecurityException) cause;
+                cause = cause.getCause();
+            }
+            throw new IOException("connection failed: cycle?",x);
+        }
+        LOG.fine("connected.");
+    }
+
+    // If this is a self-linking namespace, this method should trigger
+    // the emission of a probe in the wrapping NamespaceInterceptor.
+    // The first call to source() in the wrapping NamespaceInterceptor
+    // causes the emission of the probe.
+    //
+    // Note: the MBeanServer returned by getSourceServer
+    //       (our private JMXRemoteNamespaceDelegate inner class)
+    //       implements a sun private interface (DynamicProbe) which is
+    //       used by the NamespaceInterceptor to determine whether it should
+    //       send a probe or not.
+    //       We needed this interface here because the NamespaceInterceptor
+    //       has otherwise no means to knows that this object has just
+    //       connected, and that a new probe should be sent.
+    //
+    // Probes work this way: the NamespaceInterceptor sets a flag and sends
+    // a queryNames() request. If a queryNames() request comes in when the flag
+    // is on, then it deduces that there is a self-linking loop - and instead
+    // of calling queryNames() on the JMXNamespace (which would cause the
+    // loop to go on) it breaks the recursion by returning the probe ObjectName.
+    // If the NamespaceInterceptor receives the probe ObjectName as result of
+    // its original queryNames() it knows that it has been looping back on
+    // itslef and throws an Exception - which will be raised through this
+    // method, thus preventing the connection to be established...
+    //
+    // More info in the com.sun.jmx.namespace.NamespaceInterceptor class
+    //
+    // XXX: TODO this probe thing is way too complex and fragile.
+    //      This *must* go away or be replaced by something simpler.
+    //      ideas are welcomed.
+    //
+    private void triggerProbe(final MBeanServerConnection msc)
+            throws MalformedObjectNameException, IOException {
+        // Query Pattern that we will send through the source server in order
+        // to detect self-linking namespaces.
+        //
+        //
+        final ObjectName pattern;
+        pattern = ObjectName.getInstance("*" +
+                JMXNamespaces.NAMESPACE_SEPARATOR + ":" +
+                JMXNamespace.TYPE_ASSIGNMENT);
+        probed = false;
+        try {
+            msc.queryNames(pattern, null);
+            probed = true;
+        } catch (AccessControlException x) {
+            // if we have an MBeanPermission missing then do nothing...
+            if (!(x.getPermission() instanceof MBeanPermission))
+                throw x;
+            PROBE_LOG.finer("Can't check for cycles: " + x);
+            probed = false; // no need to do it again...
+        }
+    }
+
+    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);
+     }
+}