jdk/src/share/classes/javax/management/namespace/JMXRemoteNamespace.java
changeset 1156 bbc2d15aaf7a
child 1222 78e3d021d528
equal deleted inserted replaced
1155:a9a142fcf1b5 1156:bbc2d15aaf7a
       
     1 /*
       
     2  * Copyright 2008 Sun Microsystems, Inc.  All Rights Reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.  Sun designates this
       
     8  * particular file as subject to the "Classpath" exception as provided
       
     9  * by Sun in the LICENSE file that accompanied this code.
       
    10  *
       
    11  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    14  * version 2 for more details (a copy is included in the LICENSE file that
       
    15  * accompanied this code).
       
    16  *
       
    17  * You should have received a copy of the GNU General Public License version
       
    18  * 2 along with this work; if not, write to the Free Software Foundation,
       
    19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    20  *
       
    21  * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
       
    22  * CA 95054 USA or visit www.sun.com if you need additional information or
       
    23  * have any questions.
       
    24  */
       
    25 
       
    26 package javax.management.namespace;
       
    27 
       
    28 import com.sun.jmx.defaults.JmxProperties;
       
    29 import com.sun.jmx.mbeanserver.Util;
       
    30 import com.sun.jmx.namespace.JMXNamespaceUtils;
       
    31 import com.sun.jmx.namespace.NamespaceInterceptor.DynamicProbe;
       
    32 import com.sun.jmx.remote.util.EnvHelp;
       
    33 
       
    34 import java.io.IOException;
       
    35 import java.security.AccessControlException;
       
    36 import java.util.HashMap;
       
    37 import java.util.Map;
       
    38 import java.util.logging.Level;
       
    39 import java.util.logging.Logger;
       
    40 
       
    41 import javax.management.AttributeChangeNotification;
       
    42 
       
    43 import javax.management.InstanceNotFoundException;
       
    44 import javax.management.ListenerNotFoundException;
       
    45 import javax.management.MBeanNotificationInfo;
       
    46 import javax.management.MBeanPermission;
       
    47 import javax.management.MBeanServerConnection;
       
    48 import javax.management.MalformedObjectNameException;
       
    49 import javax.management.Notification;
       
    50 import javax.management.NotificationBroadcasterSupport;
       
    51 import javax.management.NotificationEmitter;
       
    52 import javax.management.NotificationFilter;
       
    53 import javax.management.NotificationListener;
       
    54 import javax.management.ObjectName;
       
    55 import javax.management.event.EventClient;
       
    56 import javax.management.remote.JMXConnectionNotification;
       
    57 import javax.management.remote.JMXConnector;
       
    58 import javax.management.remote.JMXConnectorFactory;
       
    59 import javax.management.remote.JMXServiceURL;
       
    60 
       
    61 /**
       
    62  * A {@link JMXNamespace} that will connect to a remote MBeanServer
       
    63  * by creating a {@link javax.management.remote.JMXConnector} from a
       
    64  * {@link javax.management.remote.JMXServiceURL}.
       
    65  * <p>
       
    66  * You can call {@link #connect() connect()} and {@link #close close()}
       
    67  * several times. This MBean will emit an {@link AttributeChangeNotification}
       
    68  * when the value of its {@link #isConnected Connected} attribute changes.
       
    69  * </p>
       
    70  * <p>
       
    71  * The JMX Remote Namespace MBean is not connected until {@link
       
    72  * #connect() connect()} is explicitly called. The usual sequence of code to
       
    73  * create a JMX Remote Namespace is thus:
       
    74  * </p>
       
    75  * <pre>
       
    76  *     final String namespace = "mynamespace";
       
    77  *     final ObjectName name = {@link JMXNamespaces#getNamespaceObjectName
       
    78  *       JMXNamespaces.getNamespaceObjectName(namespace)};
       
    79  *     final JMXServiceURL remoteServerURL = .... ;
       
    80  *     final Map<String,Object> optionsMap = .... ;
       
    81  *     final MBeanServer masterMBeanServer = .... ;
       
    82  *     final JMXRemoteNamespace namespaceMBean = {@link #newJMXRemoteNamespace
       
    83  *        JMXRemoteNamespace.newJMXRemoteNamespace(remoteServerURL, optionsMap)};
       
    84  *     masterMBeanServer.registerMBean(namespaceMBean, name);
       
    85  *     namespaceMBean.connect();
       
    86  *     // or: masterMBeanServer.invoke(name, {@link #connect() "connect"}, null, null);
       
    87  * </pre>
       
    88  * <p>
       
    89  * The JMX Remote Namespace MBean will register for {@linkplain
       
    90  * JMXConnectionNotification JMX Connection Notifications} with its underlying
       
    91  * {@link JMXConnector}. When a JMX Connection Notification indicates that
       
    92  * the underlying connection has failed, the JMX Remote Namespace MBean
       
    93  * closes its underlying connector and switches its {@link #isConnected
       
    94  * Connected} attribute to false, emitting an {@link
       
    95  * AttributeChangeNotification}.
       
    96  * </p>
       
    97  * <p>
       
    98  * At this point, a managing application (or an administrator connected
       
    99  * through a management console) can attempt to reconnect the
       
   100  * JMX Remote Namespace MBean by calling its {@link #connect() connect()} method
       
   101  * again.
       
   102  * </p>
       
   103  * <p>Note that when the connection with the remote namespace fails, or when
       
   104  *    {@link #close} is called, then any notification subscription to
       
   105  *    MBeans registered in that namespace will be lost - unless a custom
       
   106  *    {@linkplain javax.management.event event service} supporting connection-less
       
   107  *    mode was used.
       
   108  * </p>
       
   109  * @since 1.7
       
   110  */
       
   111 public class JMXRemoteNamespace
       
   112         extends JMXNamespace
       
   113         implements JMXRemoteNamespaceMBean, NotificationEmitter {
       
   114 
       
   115     /**
       
   116      * A logger for this class.
       
   117      */
       
   118     private static final Logger LOG = JmxProperties.NAMESPACE_LOGGER;
       
   119 
       
   120     private static final Logger PROBE_LOG = Logger.getLogger(
       
   121             JmxProperties.NAMESPACE_LOGGER_NAME+".probe");
       
   122 
       
   123 
       
   124     // This connection listener is used to listen for connection events from
       
   125     // the underlying JMXConnector. It is used in particular to maintain the
       
   126     // "connected" state in this MBean.
       
   127     //
       
   128     private static class ConnectionListener implements NotificationListener {
       
   129         private final JMXRemoteNamespace handler;
       
   130         private ConnectionListener(JMXRemoteNamespace handler) {
       
   131             this.handler = handler;
       
   132         }
       
   133         public void handleNotification(Notification notification,
       
   134                 Object handback) {
       
   135             if (!(notification instanceof JMXConnectionNotification))
       
   136                 return;
       
   137             final JMXConnectionNotification cn =
       
   138                     (JMXConnectionNotification)notification;
       
   139             handler.checkState(this,cn,(JMXConnector)handback);
       
   140         }
       
   141     }
       
   142 
       
   143     // When the JMXRemoteNamespace is originally created, it is not connected,
       
   144     // which means that the source MBeanServer should be one that throws
       
   145     // exceptions for most methods.  When it is subsequently connected,
       
   146     // the methods should be forwarded to the MBeanServerConnection.
       
   147     // We handle this using MBeanServerConnectionWrapper.  The
       
   148     // MBeanServerConnection that is supplied to the constructor of
       
   149     // MBeanServerConnectionWrapper is ignored (and in fact it is null)
       
   150     // because the one that is actually used is the one supplied by the
       
   151     // override of getMBeanServerConnection().
       
   152     private static class JMXRemoteNamespaceDelegate
       
   153             extends MBeanServerConnectionWrapper
       
   154             implements DynamicProbe {
       
   155         private volatile JMXRemoteNamespace parent=null;
       
   156 
       
   157         JMXRemoteNamespaceDelegate() {
       
   158             super(null,null);
       
   159         }
       
   160         @Override
       
   161         public MBeanServerConnection getMBeanServerConnection() {
       
   162             return parent.getMBeanServerConnection();
       
   163         }
       
   164         @Override
       
   165         public ClassLoader getDefaultClassLoader() {
       
   166             return parent.getDefaultClassLoader();
       
   167         }
       
   168 
       
   169         // Because this class is instantiated in the super() call from the
       
   170         // constructor of JMXRemoteNamespace, it cannot be an inner class.
       
   171         // This method achieves the effect that an inner class would have
       
   172         // had, of giving the class a reference to the outer "this".
       
   173         synchronized void initParentOnce(JMXRemoteNamespace parent) {
       
   174             if (this.parent != null)
       
   175                 throw new UnsupportedOperationException("parent already set");
       
   176             this.parent=parent;
       
   177 
       
   178         }
       
   179 
       
   180         public boolean isProbeRequested() {
       
   181             return this.parent.isProbeRequested();
       
   182         }
       
   183     }
       
   184 
       
   185     private static final MBeanNotificationInfo connectNotification =
       
   186         new MBeanNotificationInfo(new String[] {
       
   187             AttributeChangeNotification.ATTRIBUTE_CHANGE},
       
   188             "Connected",
       
   189             "Emitted when the Connected state of this object changes");
       
   190 
       
   191     private static long seqNumber=0;
       
   192 
       
   193     private final NotificationBroadcasterSupport broadcaster;
       
   194     private final ConnectionListener listener;
       
   195     private final JMXServiceURL jmxURL;
       
   196     private final Map<String,?> optionsMap;
       
   197 
       
   198     private volatile MBeanServerConnection server = null;
       
   199     private volatile JMXConnector conn = null;
       
   200     private volatile ClassLoader defaultClassLoader = null;
       
   201     private volatile boolean probed;
       
   202 
       
   203     /**
       
   204      * Creates a new instance of {@code JMXRemoteNamespace}.
       
   205      * <p>
       
   206      * This constructor is provided for subclasses.
       
   207      * To create a new instance of {@code JMXRemoteNamespace} call
       
   208      * {@link #newJMXRemoteNamespace
       
   209      *  JMXRemoteNamespace.newJMXRemoteNamespace(sourceURL, optionsMap)}.
       
   210      * </p>
       
   211      * @param sourceURL a JMX service URL that can be used to {@linkplain
       
   212      *        #connect() connect} to the
       
   213      *        source MBean Server. The source MBean Server is the remote
       
   214      *        MBean Server which contains the MBeans that will be mirrored
       
   215      *        in this namespace.
       
   216      * @param optionsMap the options map that will be passed to the
       
   217      *        {@link JMXConnectorFactory} when {@linkplain
       
   218      *        JMXConnectorFactory#newJMXConnector creating} the
       
   219      *        {@link JMXConnector} used to {@linkplain #connect() connect}
       
   220      *        to the remote source MBean Server.  Can be null, which is
       
   221      *        equivalent to an empty map.
       
   222      * @see #newJMXRemoteNamespace JMXRemoteNamespace.newJMXRemoteNamespace
       
   223      * @see #connect
       
   224      */
       
   225     protected JMXRemoteNamespace(JMXServiceURL sourceURL,
       
   226             Map<String,?> optionsMap) {
       
   227          super(new JMXRemoteNamespaceDelegate());
       
   228         ((JMXRemoteNamespaceDelegate)super.getSourceServer()).
       
   229                 initParentOnce(this);
       
   230 
       
   231         // URL must not be null.
       
   232         this.jmxURL     = JMXNamespaceUtils.checkNonNull(sourceURL,"url");
       
   233         this.broadcaster =
       
   234             new NotificationBroadcasterSupport(connectNotification);
       
   235 
       
   236         // handles options
       
   237         this.optionsMap = JMXNamespaceUtils.unmodifiableMap(optionsMap);
       
   238 
       
   239         // handles (dis)connection events
       
   240         this.listener = new ConnectionListener(this);
       
   241 
       
   242         // XXX TODO: remove the probe, or simplify it.
       
   243         this.probed = false;
       
   244     }
       
   245 
       
   246    /**
       
   247     * Returns the {@code JMXServiceURL} that is (or will be) used to
       
   248     * connect to the remote name space. <p>
       
   249     * @see #connect
       
   250     * @return The {@code JMXServiceURL} used to connect to the remote
       
   251     *         name space.
       
   252     */
       
   253     public JMXServiceURL getJMXServiceURL() {
       
   254         return jmxURL;
       
   255     }
       
   256 
       
   257     /**
       
   258     * In this class, this method never returns {@code null}, and the
       
   259     * address returned is the {@link  #getJMXServiceURL JMXServiceURL}
       
   260     * that is used by  this object to {@linkplain #connect} to the remote
       
   261     * name space. <p>
       
   262     * This behaviour might be overriden by subclasses, if needed.
       
   263     * For instance, a subclass might want to return {@code null} if it
       
   264     * doesn't want to expose that JMXServiceURL.
       
   265     */
       
   266     public JMXServiceURL getAddress() {
       
   267         return getJMXServiceURL();
       
   268     }
       
   269 
       
   270     private Map<String,?> getEnvMap() {
       
   271         return optionsMap;
       
   272     }
       
   273 
       
   274     boolean isProbeRequested() {
       
   275         return probed==false;
       
   276     }
       
   277 
       
   278     public void addNotificationListener(NotificationListener listener,
       
   279             NotificationFilter filter, Object handback) {
       
   280         broadcaster.addNotificationListener(listener, filter, handback);
       
   281     }
       
   282 
       
   283     /**
       
   284      * A subclass that needs to send its own notifications must override
       
   285      * this method in order to return an {@link MBeanNotificationInfo
       
   286      * MBeanNotificationInfo[]} array containing both its own notification
       
   287      * infos and the notification infos of its super class. <p>
       
   288      * The implementation should probably look like:
       
   289      * <pre>
       
   290      *      final MBeanNotificationInfo[] myOwnNotifs = { .... };
       
   291      *      final MBeanNotificationInfo[] parentNotifs =
       
   292      *            super.getNotificationInfo();
       
   293      *      final Set<MBeanNotificationInfo> mergedResult =
       
   294      *            new HashSet<MBeanNotificationInfo>();
       
   295      *      mergedResult.addAll(Arrays.asList(myOwnNotifs));
       
   296      *      mergedResult.addAll(Arrays.asList(parentNotifs));
       
   297      *      return mergeResult.toArray(
       
   298      *             new MBeanNotificationInfo[mergedResult.size()]);
       
   299      * </pre>
       
   300      */
       
   301     public MBeanNotificationInfo[] getNotificationInfo() {
       
   302         return broadcaster.getNotificationInfo();
       
   303     }
       
   304 
       
   305     public void removeNotificationListener(NotificationListener listener)
       
   306     throws ListenerNotFoundException {
       
   307         broadcaster.removeNotificationListener(listener);
       
   308     }
       
   309 
       
   310     public void removeNotificationListener(NotificationListener listener,
       
   311             NotificationFilter filter, Object handback)
       
   312             throws ListenerNotFoundException {
       
   313         broadcaster.removeNotificationListener(listener, filter, handback);
       
   314     }
       
   315 
       
   316     private static synchronized long getNextSeqNumber() {
       
   317         return seqNumber++;
       
   318     }
       
   319 
       
   320 
       
   321     /**
       
   322      * Sends a notification to registered listeners. Before the notification
       
   323      * is sent, the following steps are performed:
       
   324      * <ul><li>
       
   325      * If {@code n.getSequenceNumber() <= 0} set it to the next available
       
   326      * sequence number.</li>
       
   327      * <li>If {@code n.getSource() == null}, set it to the value returned by {@link
       
   328      * #getObjectName getObjectName()}.
       
   329      * </li></ul>
       
   330      * <p>This method can be called by subclasses in order to send their own
       
   331      *    notifications.
       
   332      *    In that case, these subclasses might also need to override
       
   333      *    {@link #getNotificationInfo} in order to declare their own
       
   334      *    {@linkplain MBeanNotificationInfo notification types}.
       
   335      * </p>
       
   336      * @param n The notification to send to registered listeners.
       
   337      * @see javax.management.NotificationBroadcasterSupport
       
   338      * @see #getNotificationInfo
       
   339      **/
       
   340     protected void sendNotification(Notification n) {
       
   341         if (n.getSequenceNumber()<=0)
       
   342             n.setSequenceNumber(getNextSeqNumber());
       
   343         if (n.getSource()==null)
       
   344             n.setSource(getObjectName());
       
   345         broadcaster.sendNotification(n);
       
   346     }
       
   347 
       
   348     private void checkState(ConnectionListener listener,
       
   349                             JMXConnectionNotification cn,
       
   350                             JMXConnector emittingConnector) {
       
   351 
       
   352         // Due to the asynchronous handling of notifications, it is
       
   353         // possible that this method is called for a JMXConnector
       
   354         // (or connection) which is already closed and replaced by a newer
       
   355         // one.
       
   356         //
       
   357         // This method attempts to determine the real state of the
       
   358         // connection - which might be different from what the notification
       
   359         // says.
       
   360         //
       
   361         // This is quite complex logic - because we try not to hold any
       
   362         // lock while evaluating the true value of the connected state,
       
   363         // while anyone might also call close() or connect() from a
       
   364         // different thread.
       
   365         //
       
   366         // The method switchConnection() (called from here too) also has the
       
   367         // same kind of complex logic.
       
   368         //
       
   369         // We use the JMXConnector has a handback to the notification listener
       
   370         // (emittingConnector) in order to be able to determine whether the
       
   371         // notification concerns the current connector in use, or an older
       
   372         // one.
       
   373         //
       
   374         boolean remove = false;
       
   375 
       
   376         // whether the emittingConnector is already 'removed'
       
   377         synchronized (this) {
       
   378             if (this.conn != emittingConnector ||
       
   379                     JMXConnectionNotification.FAILED.equals(cn.getType()))
       
   380                 remove = true;
       
   381         }
       
   382 
       
   383         // We need to unregister our listener from this 'removed' connector.
       
   384         // This is the only place where we remove the listener.
       
   385         //
       
   386         if (remove) {
       
   387             try {
       
   388                 // This may fail if the connector is already closed.
       
   389                 // But better unregister anyway...
       
   390                 //
       
   391                 emittingConnector.removeConnectionNotificationListener(
       
   392                         listener,null,
       
   393                         emittingConnector);
       
   394             } catch (Exception x) {
       
   395                 LOG.log(Level.FINE,
       
   396                         "Failed to unregister connection listener"+x);
       
   397                 LOG.log(Level.FINEST,
       
   398                         "Failed to unregister connection listener",x);
       
   399             }
       
   400             try {
       
   401                 // This may fail if the connector is already closed.
       
   402                 // But better call close twice and get an exception than
       
   403                 // leaking...
       
   404                 //
       
   405                 emittingConnector.close();
       
   406             } catch (Exception x) {
       
   407                 LOG.log(Level.FINEST,
       
   408                         "Failed to close old connector " +
       
   409                         "(failure was expected): "+x);
       
   410             }
       
   411         }
       
   412 
       
   413         // Now we checked whether our current connector is still alive.
       
   414         //
       
   415         boolean closed = false;
       
   416         final JMXConnector thisconn = this.conn;
       
   417         try {
       
   418             if (thisconn != null)
       
   419                 thisconn.getConnectionId();
       
   420         } catch (IOException x) {
       
   421             LOG.finest("Connector already closed: "+x);
       
   422             closed = true;
       
   423         }
       
   424 
       
   425         // We got an IOException - the connector is not connected.
       
   426         // Need to forget it and switch our state to closed.
       
   427         //
       
   428         if (closed) {
       
   429             switchConnection(thisconn,null,null);
       
   430             try {
       
   431                 // Usually this will fail... Better call close twice
       
   432                 // and get an exception than leaking...
       
   433                 //
       
   434                 if (thisconn != emittingConnector || !remove)
       
   435                     thisconn.close();
       
   436             } catch (IOException x) {
       
   437                 LOG.log(Level.FINEST,
       
   438                         "Failed to close connector (failure was expected): "
       
   439                         +x);
       
   440             }
       
   441         }
       
   442     }
       
   443 
       
   444     private final void switchConnection(JMXConnector oldc,
       
   445                                    JMXConnector newc,
       
   446                                    MBeanServerConnection mbs) {
       
   447         boolean connect = false;
       
   448         boolean close   = false;
       
   449         synchronized (this) {
       
   450             if (oldc != conn) {
       
   451                 if (newc != null) {
       
   452                     try {
       
   453                         newc.close();
       
   454                     } catch (IOException x) {
       
   455                         LOG.log(Level.FINEST,
       
   456                                 "Failed to close connector",x);
       
   457                     }
       
   458                 }
       
   459                 return;
       
   460             }
       
   461             if (conn == null && newc != null) connect=true;
       
   462             if (newc == null && conn != null) close = true;
       
   463             conn = newc;
       
   464             server = mbs;
       
   465         }
       
   466         if (connect || close) {
       
   467             boolean oldstate = close;
       
   468             boolean newstate = connect;
       
   469             final ObjectName myName = getObjectName();
       
   470 
       
   471             // In the uncommon case where the MBean is connected before
       
   472             // being registered, myName can be null...
       
   473             // If myName is null - we use 'this' as the source instead...
       
   474             //
       
   475             final Object source = (myName==null)?this:myName;
       
   476             final AttributeChangeNotification acn =
       
   477                     new AttributeChangeNotification(source,
       
   478                     getNextSeqNumber(),System.currentTimeMillis(),
       
   479                     String.valueOf(source)+
       
   480                     (newstate?" connected":" closed"),
       
   481                     "Connected",
       
   482                     "boolean",
       
   483                     Boolean.valueOf(oldstate),
       
   484                     Boolean.valueOf(newstate));
       
   485             sendNotification(acn);
       
   486         }
       
   487     }
       
   488 
       
   489     private void closeall(JMXConnector... a) {
       
   490         for (JMXConnector c : a) {
       
   491             try {
       
   492                 if (c != null) c.close();
       
   493             } catch (Exception x) {
       
   494                 // OK: we're gonna throw the original exception later.
       
   495                 LOG.finest("Ignoring exception when closing connector: "+x);
       
   496             }
       
   497         }
       
   498     }
       
   499 
       
   500     JMXConnector connect(JMXServiceURL url, Map<String,?> env)
       
   501             throws IOException {
       
   502         final JMXConnector c = newJMXConnector(jmxURL, env);
       
   503         c.connect(env);
       
   504         return c;
       
   505     }
       
   506 
       
   507     /**
       
   508      * Creates a new JMXConnector with the specified {@code url} and
       
   509      * {@code env} options map.
       
   510      * <p>
       
   511      * This method first calls {@link JMXConnectorFactory#newJMXConnector
       
   512      * JMXConnectorFactory.newJMXConnector(jmxURL, env)} to obtain a new
       
   513      * JMX connector, and returns that.
       
   514      * </p>
       
   515      * <p>
       
   516      * A subclass of {@link JMXRemoteNamespace} can provide an implementation
       
   517      * that connects to a  sub namespace of the remote server by subclassing
       
   518      * this class in the following way:
       
   519      * <pre>
       
   520      * class JMXRemoteSubNamespace extends JMXRemoteNamespace {
       
   521      *    private final String subnamespace;
       
   522      *    JMXRemoteSubNamespace(JMXServiceURL url,
       
   523      *              Map{@code <String,?>} env, String subnamespace) {
       
   524      *        super(url,options);
       
   525      *        this.subnamespace = subnamespace;
       
   526      *    }
       
   527      *    protected JMXConnector newJMXConnector(JMXServiceURL url,
       
   528      *              Map<String,?> env) throws IOException {
       
   529      *        final JMXConnector inner = super.newJMXConnector(url,env);
       
   530      *        return {@link JMXNamespaces#narrowToNamespace(JMXConnector,String)
       
   531      *               JMXNamespaces.narrowToNamespace(inner,subnamespace)};
       
   532      *    }
       
   533      * }
       
   534      * </pre>
       
   535      * </p>
       
   536      * <p>
       
   537      * Some connectors, like the JMXMP connector server defined by the
       
   538      * version 1.2 of the JMX API may not have been upgraded to use the
       
   539      * new {@linkplain javax.management.event Event Service} defined in this
       
   540      * version of the JMX API.
       
   541      * <p>
       
   542      * In that case, and if the remote server to which this JMXRemoteNamespace
       
   543      * connects also contains namespaces, it may be necessary to configure
       
   544      * explicitly an {@linkplain
       
   545      * javax.management.event.EventClientDelegate#newForwarder()
       
   546      * Event Client Forwarder} on the remote server side, and to force the use
       
   547      * of an {@link EventClient} on this client side.
       
   548      * <br>
       
   549      * A subclass of {@link JMXRemoteNamespace} can provide an implementation
       
   550      * of {@code newJMXConnector} that will force notification subscriptions
       
   551      * to flow through an {@link EventClient} over a legacy protocol by
       
   552      * overriding this method in the following way:
       
   553      * </p>
       
   554      * <pre>
       
   555      * class JMXRemoteEventClientNamespace extends JMXRemoteNamespace {
       
   556      *    JMXRemoteSubNamespaceConnector(JMXServiceURL url,
       
   557      *              Map<String,?> env) {
       
   558      *        super(url,options);
       
   559      *    }
       
   560      *    protected JMXConnector newJMXConnector(JMXServiceURL url,
       
   561      *              Map<String,?> env) throws IOException {
       
   562      *        final JMXConnector inner = super.newJMXConnector(url,env);
       
   563      *        return {@link EventClient#withEventClient(
       
   564      *                JMXConnector) EventClient.withEventClient(inner)};
       
   565      *    }
       
   566      * }
       
   567      * </pre>
       
   568      * <p>
       
   569      * Note that the remote server also needs to provide an {@link
       
   570      * javax.management.event.EventClientDelegateMBean}: only configuring
       
   571      * the client side (this object) is not enough.<br>
       
   572      * In summary, this technique should be used if the remote server
       
   573      * supports JMX namespaces, but uses a JMX Connector Server whose
       
   574      * implementation does not transparently use the new Event Service
       
   575      * (as would be the case with the JMXMPConnectorServer implementation
       
   576      * from the reference implementation of the JMX Remote API 1.0
       
   577      * specification).
       
   578      * </p>
       
   579      * @param url  The JMXServiceURL of the remote server.
       
   580      * @param optionsMap An unmodifiable options map that will be passed to the
       
   581      *        {@link JMXConnectorFactory} when {@linkplain
       
   582      *        JMXConnectorFactory#newJMXConnector creating} the
       
   583      *        {@link JMXConnector} that can connect to the remote source
       
   584      *        MBean Server.
       
   585      * @return An unconnected JMXConnector to use to connect to the remote
       
   586      *         server
       
   587      * @throws java.io.IOException if the connector could not be created.
       
   588      * @see JMXConnectorFactory#newJMXConnector(javax.management.remote.JMXServiceURL, java.util.Map)
       
   589      * @see #JMXRemoteNamespace
       
   590      */
       
   591     protected JMXConnector newJMXConnector(JMXServiceURL url,
       
   592             Map<String,?> optionsMap) throws IOException {
       
   593         final JMXConnector c =
       
   594                 JMXConnectorFactory.newJMXConnector(jmxURL, optionsMap);
       
   595 // TODO: uncomment this when contexts are added
       
   596 //        return ClientContext.withDynamicContext(c);
       
   597         return c;
       
   598     }
       
   599 
       
   600     public void connect() throws IOException {
       
   601         if (conn != null) {
       
   602             try {
       
   603                // This is much too fragile. It must go away!
       
   604                PROBE_LOG.finest("Probing again...");
       
   605                triggerProbe(getMBeanServerConnection());
       
   606             } catch(Exception x) {
       
   607                 close();
       
   608                 Throwable cause = x;
       
   609                 // if the cause is a security exception - rethrows it...
       
   610                 while (cause != null) {
       
   611                     if (cause instanceof SecurityException)
       
   612                         throw (SecurityException) cause;
       
   613                     cause = cause.getCause();
       
   614                 }
       
   615                 throw new IOException("connection failed: cycle?",x);
       
   616             }
       
   617         }
       
   618         LOG.fine("connecting...");
       
   619         // TODO remove these traces
       
   620         // System.err.println(getInitParameter()+" connecting");
       
   621         final Map<String,Object> env =
       
   622                 new HashMap<String,Object>(getEnvMap());
       
   623         try {
       
   624             // XXX: We should probably document this...
       
   625             // This allows to specify a loader name - which will be
       
   626             // retrieved from the paret MBeanServer.
       
   627             defaultClassLoader =
       
   628                 EnvHelp.resolveServerClassLoader(env,getMBeanServer());
       
   629         } catch (InstanceNotFoundException x) {
       
   630             final IOException io =
       
   631                     new IOException("ClassLoader not found");
       
   632             io.initCause(x);
       
   633             throw io;
       
   634         }
       
   635         env.put(JMXConnectorFactory.DEFAULT_CLASS_LOADER,defaultClassLoader);
       
   636         final JMXServiceURL url = getJMXServiceURL();
       
   637         final JMXConnector aconn = connect(url,env);
       
   638         final MBeanServerConnection msc;
       
   639         try {
       
   640             msc = aconn.getMBeanServerConnection();
       
   641             aconn.addConnectionNotificationListener(listener,null,aconn);
       
   642         } catch (IOException io) {
       
   643             closeall(aconn);
       
   644             throw io;
       
   645         } catch (RuntimeException x) {
       
   646             closeall(aconn);
       
   647             throw x;
       
   648         }
       
   649 
       
   650 
       
   651         // XXX Revisit here
       
   652         // Note from the author: This business of switching connection is
       
   653         // incredibly complex. Isn't there any means to simplify it?
       
   654         //
       
   655         switchConnection(conn,aconn,msc);
       
   656         try {
       
   657            triggerProbe(msc);
       
   658         } catch(Exception x) {
       
   659             close();
       
   660             Throwable cause = x;
       
   661             // if the cause is a security exception - rethrows it...
       
   662             while (cause != null) {
       
   663                 if (cause instanceof SecurityException)
       
   664                     throw (SecurityException) cause;
       
   665                 cause = cause.getCause();
       
   666             }
       
   667             throw new IOException("connection failed: cycle?",x);
       
   668         }
       
   669         LOG.fine("connected.");
       
   670     }
       
   671 
       
   672     // If this is a self-linking namespace, this method should trigger
       
   673     // the emission of a probe in the wrapping NamespaceInterceptor.
       
   674     // The first call to source() in the wrapping NamespaceInterceptor
       
   675     // causes the emission of the probe.
       
   676     //
       
   677     // Note: the MBeanServer returned by getSourceServer
       
   678     //       (our private JMXRemoteNamespaceDelegate inner class)
       
   679     //       implements a sun private interface (DynamicProbe) which is
       
   680     //       used by the NamespaceInterceptor to determine whether it should
       
   681     //       send a probe or not.
       
   682     //       We needed this interface here because the NamespaceInterceptor
       
   683     //       has otherwise no means to knows that this object has just
       
   684     //       connected, and that a new probe should be sent.
       
   685     //
       
   686     // Probes work this way: the NamespaceInterceptor sets a flag and sends
       
   687     // a queryNames() request. If a queryNames() request comes in when the flag
       
   688     // is on, then it deduces that there is a self-linking loop - and instead
       
   689     // of calling queryNames() on the JMXNamespace (which would cause the
       
   690     // loop to go on) it breaks the recursion by returning the probe ObjectName.
       
   691     // If the NamespaceInterceptor receives the probe ObjectName as result of
       
   692     // its original queryNames() it knows that it has been looping back on
       
   693     // itslef and throws an Exception - which will be raised through this
       
   694     // method, thus preventing the connection to be established...
       
   695     //
       
   696     // More info in the com.sun.jmx.namespace.NamespaceInterceptor class
       
   697     //
       
   698     // XXX: TODO this probe thing is way too complex and fragile.
       
   699     //      This *must* go away or be replaced by something simpler.
       
   700     //      ideas are welcomed.
       
   701     //
       
   702     private void triggerProbe(final MBeanServerConnection msc)
       
   703             throws MalformedObjectNameException, IOException {
       
   704         // Query Pattern that we will send through the source server in order
       
   705         // to detect self-linking namespaces.
       
   706         //
       
   707         //
       
   708         final ObjectName pattern;
       
   709         pattern = ObjectName.getInstance("*" +
       
   710                 JMXNamespaces.NAMESPACE_SEPARATOR + ":" +
       
   711                 JMXNamespace.TYPE_ASSIGNMENT);
       
   712         probed = false;
       
   713         try {
       
   714             msc.queryNames(pattern, null);
       
   715             probed = true;
       
   716         } catch (AccessControlException x) {
       
   717             // if we have an MBeanPermission missing then do nothing...
       
   718             if (!(x.getPermission() instanceof MBeanPermission))
       
   719                 throw x;
       
   720             PROBE_LOG.finer("Can't check for cycles: " + x);
       
   721             probed = false; // no need to do it again...
       
   722         }
       
   723     }
       
   724 
       
   725     public void close() throws IOException {
       
   726         if (conn == null) return;
       
   727         LOG.fine("closing...");
       
   728         // System.err.println(toString()+": closing...");
       
   729         conn.close();
       
   730         // System.err.println(toString()+": connector closed");
       
   731         switchConnection(conn,null,null);
       
   732         LOG.fine("closed.");
       
   733         // System.err.println(toString()+": closed");
       
   734     }
       
   735 
       
   736     MBeanServerConnection getMBeanServerConnection() {
       
   737         if (conn == null)
       
   738             throw newRuntimeIOException("getMBeanServerConnection: not connected");
       
   739         return server;
       
   740     }
       
   741 
       
   742     // Better than throwing UndeclaredThrowableException ...
       
   743     private RuntimeException newRuntimeIOException(String msg) {
       
   744         final IllegalStateException illegal = new IllegalStateException(msg);
       
   745         return Util.newRuntimeIOException(new IOException(msg,illegal));
       
   746     }
       
   747 
       
   748     /**
       
   749      * Returns the default class loader used by the underlying
       
   750      * {@link JMXConnector}.
       
   751      * @return the default class loader used when communicating with the
       
   752      *         remote source MBean server.
       
   753      **/
       
   754     ClassLoader getDefaultClassLoader() {
       
   755         if (conn == null)
       
   756             throw newRuntimeIOException("getMBeanServerConnection: not connected");
       
   757         return defaultClassLoader;
       
   758     }
       
   759 
       
   760     public boolean isConnected() {
       
   761         // This is a pleonasm
       
   762         return (conn != null) && (server != null);
       
   763     }
       
   764 
       
   765 
       
   766     /**
       
   767      * This name space handler will automatically {@link #close} its
       
   768      * connection with the remote source in {@code preDeregister}.
       
   769      **/
       
   770     @Override
       
   771     public void preDeregister() throws Exception {
       
   772         try {
       
   773             close();
       
   774         } catch (IOException x) {
       
   775             LOG.fine("Failed to close properly - exception ignored: " + x);
       
   776             LOG.log(Level.FINEST,
       
   777                     "Failed to close properly - exception ignored",x);
       
   778         }
       
   779         super.preDeregister();
       
   780     }
       
   781 
       
   782    /**
       
   783     * This method calls {@link
       
   784     * javax.management.MBeanServerConnection#getMBeanCount
       
   785     * getMBeanCount()} on the remote namespace.
       
   786     * @throws java.io.IOException if an {@link IOException} is raised when
       
   787     *         communicating with the remote source namespace.
       
   788     */
       
   789     @Override
       
   790     public Integer getMBeanCount() throws IOException {
       
   791         return getMBeanServerConnection().getMBeanCount();
       
   792     }
       
   793 
       
   794    /**
       
   795     * This method returns the result of calling {@link
       
   796     * javax.management.MBeanServerConnection#getDomains
       
   797     * getDomains()} on the remote namespace.
       
   798     * @throws java.io.IOException if an {@link IOException} is raised when
       
   799     *         communicating with the remote source namespace.
       
   800     */
       
   801     @Override
       
   802    public String[] getDomains() throws IOException {
       
   803        return getMBeanServerConnection().getDomains();
       
   804     }
       
   805 
       
   806    /**
       
   807     * This method returns the result of calling {@link
       
   808     * javax.management.MBeanServerConnection#getDefaultDomain
       
   809     * getDefaultDomain()} on the remote namespace.
       
   810     * @throws java.io.IOException if an {@link IOException} is raised when
       
   811     *         communicating with the remote source namespace.
       
   812     */
       
   813     @Override
       
   814     public String getDefaultDomain() throws IOException {
       
   815         return getMBeanServerConnection().getDefaultDomain();
       
   816     }
       
   817 
       
   818     /**
       
   819      * Creates a new instance of {@code JMXRemoteNamespace}.
       
   820      * @param sourceURL a JMX service URL that can be used to connect to the
       
   821      *        source MBean Server. The source MBean Server is the remote
       
   822      *        MBean Server which contains the MBeans that will be mirrored
       
   823      *        in this namespace.
       
   824      * @param optionsMap An options map that will be passed to the
       
   825      *        {@link JMXConnectorFactory} when {@linkplain
       
   826      *        JMXConnectorFactory#newJMXConnector creating} the
       
   827      *        {@link JMXConnector} used to connect to the remote source
       
   828      *        MBean Server.  Can be null, which is equivalent to an empty map.
       
   829      * @see #JMXRemoteNamespace JMXRemoteNamespace(sourceURL,optionsMap)
       
   830      * @see JMXConnectorFactory#newJMXConnector(javax.management.remote.JMXServiceURL, java.util.Map)
       
   831      */
       
   832      public static JMXRemoteNamespace newJMXRemoteNamespace(
       
   833              JMXServiceURL sourceURL,
       
   834              Map<String,?> optionsMap) {
       
   835          return new JMXRemoteNamespace(sourceURL, optionsMap);
       
   836      }
       
   837 }