jdk/src/share/classes/javax/management/remote/rmi/RMIConnectorServer.java
changeset 2 90ce3da70b43
child 526 61ba2d5ea9da
equal deleted inserted replaced
0:fd16c54261b3 2:90ce3da70b43
       
     1 /*
       
     2  * Copyright 2002-2007 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.remote.rmi;
       
    27 
       
    28 
       
    29 import com.sun.jmx.remote.security.MBeanServerFileAccessController;
       
    30 import com.sun.jmx.remote.util.ClassLogger;
       
    31 import com.sun.jmx.remote.util.EnvHelp;
       
    32 
       
    33 import java.io.ByteArrayOutputStream;
       
    34 import java.io.IOException;
       
    35 import java.io.ObjectOutputStream;
       
    36 import java.net.MalformedURLException;
       
    37 import java.rmi.server.RMIClientSocketFactory;
       
    38 import java.rmi.server.RMIServerSocketFactory;
       
    39 import java.util.Collections;
       
    40 import java.util.HashMap;
       
    41 import java.util.HashSet;
       
    42 import java.util.Hashtable;
       
    43 import java.util.Map;
       
    44 import java.util.Set;
       
    45 
       
    46 import javax.management.InstanceNotFoundException;
       
    47 import javax.management.MBeanServer;
       
    48 
       
    49 import javax.management.remote.JMXConnectionNotification;
       
    50 import javax.management.remote.JMXConnector;
       
    51 import javax.management.remote.JMXConnectorServer;
       
    52 import javax.management.remote.JMXServiceURL;
       
    53 import javax.management.remote.MBeanServerForwarder;
       
    54 
       
    55 import javax.naming.InitialContext;
       
    56 import javax.naming.NamingException;
       
    57 
       
    58 /**
       
    59  * <p>A JMX API connector server that creates RMI-based connections
       
    60  * from remote clients.  Usually, such connector servers are made
       
    61  * using {@link javax.management.remote.JMXConnectorServerFactory
       
    62  * JMXConnectorServerFactory}.  However, specialized applications can
       
    63  * use this class directly, for example with an {@link RMIServerImpl}
       
    64  * object.</p>
       
    65  *
       
    66  * @since 1.5
       
    67  */
       
    68 public class RMIConnectorServer extends JMXConnectorServer {
       
    69     /**
       
    70      * <p>Name of the attribute that specifies whether the {@link
       
    71      * RMIServer} stub that represents an RMI connector server should
       
    72      * override an existing stub at the same address.  The value
       
    73      * associated with this attribute, if any, should be a string that
       
    74      * is equal, ignoring case, to <code>"true"</code> or
       
    75      * <code>"false"</code>.  The default value is false.</p>
       
    76      */
       
    77     public static final String JNDI_REBIND_ATTRIBUTE =
       
    78         "jmx.remote.jndi.rebind";
       
    79 
       
    80     /**
       
    81      * <p>Name of the attribute that specifies the {@link
       
    82      * RMIClientSocketFactory} for the RMI objects created in
       
    83      * conjunction with this connector. The value associated with this
       
    84      * attribute must be of type <code>RMIClientSocketFactory</code> and can
       
    85      * only be specified in the <code>Map</code> argument supplied when
       
    86      * creating a connector server.</p>
       
    87      */
       
    88     public static final String RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE =
       
    89         "jmx.remote.rmi.client.socket.factory";
       
    90 
       
    91     /**
       
    92      * <p>Name of the attribute that specifies the {@link
       
    93      * RMIServerSocketFactory} for the RMI objects created in
       
    94      * conjunction with this connector. The value associated with this
       
    95      * attribute must be of type <code>RMIServerSocketFactory</code> and can
       
    96      * only be specified in the <code>Map</code> argument supplied when
       
    97      * creating a connector server.</p>
       
    98      */
       
    99     public static final String RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE =
       
   100         "jmx.remote.rmi.server.socket.factory";
       
   101 
       
   102     /**
       
   103      * <p>Makes an <code>RMIConnectorServer</code>.
       
   104      * This is equivalent to calling {@link #RMIConnectorServer(
       
   105      * JMXServiceURL,Map,RMIServerImpl,MBeanServer)
       
   106      * RMIConnectorServer(directoryURL,environment,null,null)}</p>
       
   107      *
       
   108      * @param url the URL defining how to create the connector server.
       
   109      * Cannot be null.
       
   110      *
       
   111      * @param environment attributes governing the creation and
       
   112      * storing of the RMI object.  Can be null, which is equivalent to
       
   113      * an empty Map.
       
   114      *
       
   115      * @exception IllegalArgumentException if <code>url</code> is null.
       
   116      *
       
   117      * @exception MalformedURLException if <code>url</code> does not
       
   118      * conform to the syntax for an RMI connector, or if its protocol
       
   119      * is not recognized by this implementation. Only "rmi" and "iiop"
       
   120      * are valid when this constructor is used.
       
   121      *
       
   122      * @exception IOException if the connector server cannot be created
       
   123      * for some reason or if it is inevitable that its {@link #start()
       
   124      * start} method will fail.
       
   125      */
       
   126     public RMIConnectorServer(JMXServiceURL url, Map<String,?> environment)
       
   127             throws IOException {
       
   128         this(url, environment, (MBeanServer) null);
       
   129     }
       
   130 
       
   131     /**
       
   132      * <p>Makes an <code>RMIConnectorServer</code> for the given MBean
       
   133      * server.
       
   134      * This is equivalent to calling {@link #RMIConnectorServer(
       
   135      * JMXServiceURL,Map,RMIServerImpl,MBeanServer)
       
   136      * RMIConnectorServer(directoryURL,environment,null,mbeanServer)}</p>
       
   137      *
       
   138      * @param url the URL defining how to create the connector server.
       
   139      * Cannot be null.
       
   140      *
       
   141      * @param environment attributes governing the creation and
       
   142      * storing of the RMI object.  Can be null, which is equivalent to
       
   143      * an empty Map.
       
   144      *
       
   145      * @param mbeanServer the MBean server to which the new connector
       
   146      * server is attached, or null if it will be attached by being
       
   147      * registered as an MBean in the MBean server.
       
   148      *
       
   149      * @exception IllegalArgumentException if <code>url</code> is null.
       
   150      *
       
   151      * @exception MalformedURLException if <code>url</code> does not
       
   152      * conform to the syntax for an RMI connector, or if its protocol
       
   153      * is not recognized by this implementation. Only "rmi" and "iiop"
       
   154      * are valid when this constructor is used.
       
   155      *
       
   156      * @exception IOException if the connector server cannot be created
       
   157      * for some reason or if it is inevitable that its {@link #start()
       
   158      * start} method will fail.
       
   159      */
       
   160     public RMIConnectorServer(JMXServiceURL url, Map<String,?> environment,
       
   161                               MBeanServer mbeanServer)
       
   162             throws IOException {
       
   163         this(url, environment, (RMIServerImpl) null, mbeanServer);
       
   164     }
       
   165 
       
   166     /**
       
   167      * <p>Makes an <code>RMIConnectorServer</code> for the given MBean
       
   168      * server.</p>
       
   169      *
       
   170      * @param url the URL defining how to create the connector server.
       
   171      * Cannot be null.
       
   172      *
       
   173      * @param environment attributes governing the creation and
       
   174      * storing of the RMI object.  Can be null, which is equivalent to
       
   175      * an empty Map.
       
   176      *
       
   177      * @param rmiServerImpl An implementation of the RMIServer interface,
       
   178      *  consistent with the protocol type specified in <var>url</var>.
       
   179      *  If this parameter is non null, the protocol type specified by
       
   180      *  <var>url</var> is not constrained, and is assumed to be valid.
       
   181      *  Otherwise, only "rmi" and "iiop" will be recognized.
       
   182      *
       
   183      * @param mbeanServer the MBean server to which the new connector
       
   184      * server is attached, or null if it will be attached by being
       
   185      * registered as an MBean in the MBean server.
       
   186      *
       
   187      * @exception IllegalArgumentException if <code>url</code> is null.
       
   188      *
       
   189      * @exception MalformedURLException if <code>url</code> does not
       
   190      * conform to the syntax for an RMI connector, or if its protocol
       
   191      * is not recognized by this implementation. Only "rmi" and "iiop"
       
   192      * are recognized when <var>rmiServerImpl</var> is null.
       
   193      *
       
   194      * @exception IOException if the connector server cannot be created
       
   195      * for some reason or if it is inevitable that its {@link #start()
       
   196      * start} method will fail.
       
   197      *
       
   198      * @see #start
       
   199      */
       
   200     public RMIConnectorServer(JMXServiceURL url, Map<String,?> environment,
       
   201                               RMIServerImpl rmiServerImpl,
       
   202                               MBeanServer mbeanServer)
       
   203             throws IOException {
       
   204         super(mbeanServer);
       
   205 
       
   206         if (url == null) throw new
       
   207             IllegalArgumentException("Null JMXServiceURL");
       
   208         if (rmiServerImpl == null) {
       
   209             final String prt = url.getProtocol();
       
   210             if (prt == null || !(prt.equals("rmi") || prt.equals("iiop"))) {
       
   211                 final String msg = "Invalid protocol type: " + prt;
       
   212                 throw new MalformedURLException(msg);
       
   213             }
       
   214             final String urlPath = url.getURLPath();
       
   215             if (!urlPath.equals("")
       
   216                 && !urlPath.equals("/")
       
   217                 && !urlPath.startsWith("/jndi/")) {
       
   218                 final String msg = "URL path must be empty or start with " +
       
   219                     "/jndi/";
       
   220                 throw new MalformedURLException(msg);
       
   221             }
       
   222         }
       
   223 
       
   224         if (environment == null)
       
   225             this.attributes = Collections.emptyMap();
       
   226         else {
       
   227             EnvHelp.checkAttributes(environment);
       
   228             this.attributes = Collections.unmodifiableMap(environment);
       
   229         }
       
   230 
       
   231         this.address = url;
       
   232         this.rmiServerImpl = rmiServerImpl;
       
   233     }
       
   234 
       
   235     /**
       
   236      * <p>Returns a client stub for this connector server.  A client
       
   237      * stub is a serializable object whose {@link
       
   238      * JMXConnector#connect(Map) connect} method can be used to make
       
   239      * one new connection to this connector server.</p>
       
   240      *
       
   241      * @param env client connection parameters of the same sort that
       
   242      * could be provided to {@link JMXConnector#connect(Map)
       
   243      * JMXConnector.connect(Map)}.  Can be null, which is equivalent
       
   244      * to an empty map.
       
   245      *
       
   246      * @return a client stub that can be used to make a new connection
       
   247      * to this connector server.
       
   248      *
       
   249      * @exception UnsupportedOperationException if this connector
       
   250      * server does not support the generation of client stubs.
       
   251      *
       
   252      * @exception IllegalStateException if the JMXConnectorServer is
       
   253      * not started (see {@link #isActive()}).
       
   254      *
       
   255      * @exception IOException if a communications problem means that a
       
   256      * stub cannot be created.
       
   257      **/
       
   258     public JMXConnector toJMXConnector(Map<String,?> env) throws IOException {
       
   259         // The serialized for of rmiServerImpl is automatically
       
   260         // a RMI server stub.
       
   261         if (!isActive()) throw new
       
   262             IllegalStateException("Connector is not active");
       
   263 
       
   264         // Merge maps
       
   265         Map<String, Object> usemap = new HashMap<String, Object>(
       
   266                 (this.attributes==null)?Collections.<String, Object>emptyMap():
       
   267                     this.attributes);
       
   268 
       
   269         if (env != null) {
       
   270             EnvHelp.checkAttributes(env);
       
   271             usemap.putAll(env);
       
   272         }
       
   273 
       
   274         usemap = EnvHelp.filterAttributes(usemap);
       
   275 
       
   276         final RMIServer stub=(RMIServer)rmiServerImpl.toStub();
       
   277 
       
   278         return new RMIConnector(stub, usemap);
       
   279     }
       
   280 
       
   281     /**
       
   282      * <p>Activates the connector server, that is starts listening for
       
   283      * client connections.  Calling this method when the connector
       
   284      * server is already active has no effect.  Calling this method
       
   285      * when the connector server has been stopped will generate an
       
   286      * <code>IOException</code>.</p>
       
   287      *
       
   288      * <p>The behavior of this method when called for the first time
       
   289      * depends on the parameters that were supplied at construction,
       
   290      * as described below.</p>
       
   291      *
       
   292      * <p>First, an object of a subclass of {@link RMIServerImpl} is
       
   293      * required, to export the connector server through RMI:</p>
       
   294      *
       
   295      * <ul>
       
   296      *
       
   297      * <li>If an <code>RMIServerImpl</code> was supplied to the
       
   298      * constructor, it is used.
       
   299      *
       
   300      * <li>Otherwise, if the protocol part of the
       
   301      * <code>JMXServiceURL</code> supplied to the constructor was
       
   302      * <code>iiop</code>, an object of type {@link RMIIIOPServerImpl}
       
   303      * is created.
       
   304      *
       
   305      * <li>Otherwise, if the <code>JMXServiceURL</code>
       
   306      * was null, or its protocol part was <code>rmi</code>, an object
       
   307      * of type {@link RMIJRMPServerImpl} is created.
       
   308      *
       
   309      * <li>Otherwise, the implementation can create an
       
   310      * implementation-specific {@link RMIServerImpl} or it can throw
       
   311      * {@link MalformedURLException}.
       
   312      *
       
   313      * </ul>
       
   314      *
       
   315      * <p>If the given address includes a JNDI directory URL as
       
   316      * specified in the package documentation for {@link
       
   317      * javax.management.remote.rmi}, then this
       
   318      * <code>RMIConnectorServer</code> will bootstrap by binding the
       
   319      * <code>RMIServerImpl</code> to the given address.</p>
       
   320      *
       
   321      * <p>If the URL path part of the <code>JMXServiceURL</code> was
       
   322      * empty or a single slash (<code>/</code>), then the RMI object
       
   323      * will not be bound to a directory.  Instead, a reference to it
       
   324      * will be encoded in the URL path of the RMIConnectorServer
       
   325      * address (returned by {@link #getAddress()}).  The encodings for
       
   326      * <code>rmi</code> and <code>iiop</code> are described in the
       
   327      * package documentation for {@link
       
   328      * javax.management.remote.rmi}.</p>
       
   329      *
       
   330      * <p>The behavior when the URL path is neither empty nor a JNDI
       
   331      * directory URL, or when the protocol is neither <code>rmi</code>
       
   332      * nor <code>iiop</code>, is implementation defined, and may
       
   333      * include throwing {@link MalformedURLException} when the
       
   334      * connector server is created or when it is started.</p>
       
   335      *
       
   336      * @exception IllegalStateException if the connector server has
       
   337      * not been attached to an MBean server.
       
   338      * @exception IOException if the connector server cannot be
       
   339      * started.
       
   340      */
       
   341     public synchronized void start() throws IOException {
       
   342         final boolean tracing = logger.traceOn();
       
   343 
       
   344         if (state == STARTED) {
       
   345             if (tracing) logger.trace("start", "already started");
       
   346             return;
       
   347         } else if (state == STOPPED) {
       
   348             if (tracing) logger.trace("start", "already stopped");
       
   349             throw new IOException("The server has been stopped.");
       
   350         }
       
   351 
       
   352         if (getMBeanServer() == null)
       
   353             throw new IllegalStateException("This connector server is not " +
       
   354                                             "attached to an MBean server");
       
   355 
       
   356         // Check the internal access file property to see
       
   357         // if an MBeanServerForwarder is to be provided
       
   358         //
       
   359         if (attributes != null) {
       
   360             // Check if access file property is specified
       
   361             //
       
   362             String accessFile =
       
   363                 (String) attributes.get("jmx.remote.x.access.file");
       
   364             if (accessFile != null) {
       
   365                 // Access file property specified, create an instance
       
   366                 // of the MBeanServerFileAccessController class
       
   367                 //
       
   368                 MBeanServerForwarder mbsf = null;
       
   369                 try {
       
   370                     mbsf = new MBeanServerFileAccessController(accessFile);
       
   371                 } catch (IOException e) {
       
   372                     throw EnvHelp.initCause(
       
   373                         new IllegalArgumentException(e.getMessage()), e);
       
   374                 }
       
   375                 // Set the MBeanServerForwarder
       
   376                 //
       
   377                 setMBeanServerForwarder(mbsf);
       
   378             }
       
   379         }
       
   380 
       
   381         try {
       
   382             if (tracing) logger.trace("start", "setting default class loader");
       
   383             defaultClassLoader =
       
   384                 EnvHelp.resolveServerClassLoader(attributes, getMBeanServer());
       
   385         } catch (InstanceNotFoundException infc) {
       
   386             IllegalArgumentException x = new
       
   387                 IllegalArgumentException("ClassLoader not found: "+infc);
       
   388             throw EnvHelp.initCause(x,infc);
       
   389         }
       
   390 
       
   391         if (tracing) logger.trace("start", "setting RMIServer object");
       
   392         final RMIServerImpl rmiServer;
       
   393 
       
   394         if (rmiServerImpl != null)
       
   395             rmiServer = rmiServerImpl;
       
   396         else
       
   397             rmiServer = newServer();
       
   398 
       
   399         rmiServer.setMBeanServer(getMBeanServer());
       
   400         rmiServer.setDefaultClassLoader(defaultClassLoader);
       
   401         rmiServer.setRMIConnectorServer(this);
       
   402         rmiServer.export();
       
   403 
       
   404         try {
       
   405             if (tracing) logger.trace("start", "getting RMIServer object to export");
       
   406             final RMIServer objref = objectToBind(rmiServer, attributes);
       
   407 
       
   408             if (address != null && address.getURLPath().startsWith("/jndi/")) {
       
   409                 final String jndiUrl = address.getURLPath().substring(6);
       
   410 
       
   411                 if (tracing)
       
   412                     logger.trace("start", "Using external directory: " + jndiUrl);
       
   413 
       
   414                 final boolean rebind = EnvHelp.computeBooleanFromString(
       
   415                     attributes,
       
   416                     JNDI_REBIND_ATTRIBUTE);
       
   417 
       
   418                 if (tracing)
       
   419                     logger.trace("start", JNDI_REBIND_ATTRIBUTE + "=" + rebind);
       
   420 
       
   421                 try {
       
   422                     if (tracing) logger.trace("start", "binding to " + jndiUrl);
       
   423 
       
   424                     final Hashtable usemap = EnvHelp.mapToHashtable(attributes);
       
   425 
       
   426                     bind(jndiUrl, usemap, objref, rebind);
       
   427 
       
   428                     boundJndiUrl = jndiUrl;
       
   429                 } catch (NamingException e) {
       
   430                     // fit e in the nested exception if we are on 1.4
       
   431                     throw newIOException("Cannot bind to URL ["+jndiUrl+"]: "
       
   432                                          + e, e);
       
   433                 }
       
   434             } else {
       
   435                 // if jndiURL is null, we must encode the stub into the URL.
       
   436                 if (tracing) logger.trace("start", "Encoding URL");
       
   437 
       
   438                 encodeStubInAddress(objref, attributes);
       
   439 
       
   440                 if (tracing) logger.trace("start", "Encoded URL: " + this.address);
       
   441             }
       
   442         } catch (Exception e) {
       
   443             try {
       
   444                 rmiServer.close();
       
   445             } catch (Exception x) {
       
   446                 // OK: we are already throwing another exception
       
   447             }
       
   448             if (e instanceof RuntimeException)
       
   449                 throw (RuntimeException) e;
       
   450             else if (e instanceof IOException)
       
   451                 throw (IOException) e;
       
   452             else
       
   453                 throw newIOException("Got unexpected exception while " +
       
   454                                      "starting the connector server: "
       
   455                                      + e, e);
       
   456         }
       
   457 
       
   458         rmiServerImpl = rmiServer;
       
   459 
       
   460         synchronized(openedServers) {
       
   461             openedServers.add(this);
       
   462         }
       
   463 
       
   464         state = STARTED;
       
   465 
       
   466         if (tracing) {
       
   467             logger.trace("start", "Connector Server Address = " + address);
       
   468             logger.trace("start", "started.");
       
   469         }
       
   470     }
       
   471 
       
   472     /**
       
   473      * <p>Deactivates the connector server, that is, stops listening for
       
   474      * client connections.  Calling this method will also close all
       
   475      * client connections that were made by this server.  After this
       
   476      * method returns, whether normally or with an exception, the
       
   477      * connector server will not create any new client
       
   478      * connections.</p>
       
   479      *
       
   480      * <p>Once a connector server has been stopped, it cannot be started
       
   481      * again.</p>
       
   482      *
       
   483      * <p>Calling this method when the connector server has already
       
   484      * been stopped has no effect.  Calling this method when the
       
   485      * connector server has not yet been started will disable the
       
   486      * connector server object permanently.</p>
       
   487      *
       
   488      * <p>If closing a client connection produces an exception, that
       
   489      * exception is not thrown from this method.  A {@link
       
   490      * JMXConnectionNotification} is emitted from this MBean with the
       
   491      * connection ID of the connection that could not be closed.</p>
       
   492      *
       
   493      * <p>Closing a connector server is a potentially slow operation.
       
   494      * For example, if a client machine with an open connection has
       
   495      * crashed, the close operation might have to wait for a network
       
   496      * protocol timeout.  Callers that do not want to block in a close
       
   497      * operation should do it in a separate thread.</p>
       
   498      *
       
   499      * <p>This method calls the method {@link RMIServerImpl#close()
       
   500      * close} on the connector server's <code>RMIServerImpl</code>
       
   501      * object.</p>
       
   502      *
       
   503      * <p>If the <code>RMIServerImpl</code> was bound to a JNDI
       
   504      * directory by the {@link #start() start} method, it is unbound
       
   505      * from the directory by this method.</p>
       
   506      *
       
   507      * @exception IOException if the server cannot be closed cleanly,
       
   508      * or if the <code>RMIServerImpl</code> cannot be unbound from the
       
   509      * directory.  When this exception is thrown, the server has
       
   510      * already attempted to close all client connections, if
       
   511      * appropriate; to call {@link RMIServerImpl#close()}; and to
       
   512      * unbind the <code>RMIServerImpl</code> from its directory, if
       
   513      * appropriate.  All client connections are closed except possibly
       
   514      * those that generated exceptions when the server attempted to
       
   515      * close them.
       
   516      */
       
   517     public void stop() throws IOException {
       
   518         final boolean tracing = logger.traceOn();
       
   519 
       
   520         synchronized (this) {
       
   521             if (state == STOPPED) {
       
   522                 if (tracing) logger.trace("stop","already stopped.");
       
   523                 return;
       
   524             } else if (state == CREATED) {
       
   525                 if (tracing) logger.trace("stop","not started yet.");
       
   526             }
       
   527 
       
   528             if (tracing) logger.trace("stop", "stopping.");
       
   529             state = STOPPED;
       
   530         }
       
   531 
       
   532         synchronized(openedServers) {
       
   533             openedServers.remove(this);
       
   534         }
       
   535 
       
   536         IOException exception = null;
       
   537 
       
   538         // rmiServerImpl can be null if stop() called without start()
       
   539         if (rmiServerImpl != null) {
       
   540             try {
       
   541                 if (tracing) logger.trace("stop", "closing RMI server.");
       
   542                 rmiServerImpl.close();
       
   543             } catch (IOException e) {
       
   544                 if (tracing) logger.trace("stop", "failed to close RMI server: " + e);
       
   545                 if (logger.debugOn()) logger.debug("stop",e);
       
   546                 exception = e;
       
   547             }
       
   548         }
       
   549 
       
   550         if (boundJndiUrl != null) {
       
   551             try {
       
   552                 if (tracing)
       
   553                     logger.trace("stop",
       
   554                           "unbind from external directory: " + boundJndiUrl);
       
   555 
       
   556                 final Hashtable usemap = EnvHelp.mapToHashtable(attributes);
       
   557 
       
   558                 InitialContext ctx =
       
   559                     new InitialContext(usemap);
       
   560 
       
   561                 ctx.unbind(boundJndiUrl);
       
   562 
       
   563                 ctx.close();
       
   564             } catch (NamingException e) {
       
   565                 if (tracing) logger.trace("stop", "failed to unbind RMI server: "+e);
       
   566                 if (logger.debugOn()) logger.debug("stop",e);
       
   567                 // fit e in as the nested exception if we are on 1.4
       
   568                 if (exception == null)
       
   569                     exception = newIOException("Cannot bind to URL: " + e, e);
       
   570             }
       
   571         }
       
   572 
       
   573         if (exception != null) throw exception;
       
   574 
       
   575         if (tracing) logger.trace("stop", "stopped");
       
   576     }
       
   577 
       
   578     public synchronized boolean isActive() {
       
   579         return (state == STARTED);
       
   580     }
       
   581 
       
   582     public JMXServiceURL getAddress() {
       
   583         if (!isActive())
       
   584             return null;
       
   585         return address;
       
   586     }
       
   587 
       
   588     public Map<String,?> getAttributes() {
       
   589         Map<String, ?> map = EnvHelp.filterAttributes(attributes);
       
   590         return Collections.unmodifiableMap(map);
       
   591     }
       
   592 
       
   593     public synchronized
       
   594         void setMBeanServerForwarder(MBeanServerForwarder mbsf) {
       
   595         super.setMBeanServerForwarder(mbsf);
       
   596         if (rmiServerImpl != null)
       
   597             rmiServerImpl.setMBeanServer(getMBeanServer());
       
   598     }
       
   599 
       
   600     /* We repeat the definitions of connection{Opened,Closed,Failed}
       
   601        here so that they are accessible to other classes in this package
       
   602        even though they have protected access.  */
       
   603 
       
   604     protected void connectionOpened(String connectionId, String message,
       
   605                                     Object userData) {
       
   606         super.connectionOpened(connectionId, message, userData);
       
   607     }
       
   608 
       
   609     protected void connectionClosed(String connectionId, String message,
       
   610                                     Object userData) {
       
   611         super.connectionClosed(connectionId, message, userData);
       
   612     }
       
   613 
       
   614     protected void connectionFailed(String connectionId, String message,
       
   615                                     Object userData) {
       
   616         super.connectionFailed(connectionId, message, userData);
       
   617     }
       
   618 
       
   619     /**
       
   620      * Bind a stub to a registry.
       
   621      * @param jndiUrl URL of the stub in the registry, extracted
       
   622      *        from the <code>JMXServiceURL</code>.
       
   623      * @param attributes A Hashtable containing environment parameters,
       
   624      *        built from the Map specified at this object creation.
       
   625      * @param rmiServer The object to bind in the registry
       
   626      * @param rebind true if the object must be rebound.
       
   627      **/
       
   628     void bind(String jndiUrl, Hashtable attributes,
       
   629               RMIServer rmiServer, boolean rebind)
       
   630         throws NamingException, MalformedURLException {
       
   631         // if jndiURL is not null, we nust bind the stub to a
       
   632         // directory.
       
   633         InitialContext ctx =
       
   634             new InitialContext(attributes);
       
   635 
       
   636         if (rebind)
       
   637             ctx.rebind(jndiUrl, rmiServer);
       
   638         else
       
   639             ctx.bind(jndiUrl, rmiServer);
       
   640         ctx.close();
       
   641     }
       
   642 
       
   643     /**
       
   644      * Creates a new RMIServerImpl.
       
   645      **/
       
   646     RMIServerImpl newServer() throws IOException {
       
   647         final boolean iiop = isIiopURL(address,true);
       
   648         final int port;
       
   649         if (address == null)
       
   650             port = 0;
       
   651         else
       
   652             port = address.getPort();
       
   653         if (iiop)
       
   654             return newIIOPServer(attributes);
       
   655         else
       
   656             return newJRMPServer(attributes, port);
       
   657     }
       
   658 
       
   659     /**
       
   660      * Encode a stub into the JMXServiceURL.
       
   661      * @param rmiServer The stub object to encode in the URL
       
   662      * @param attributes A Map containing environment parameters,
       
   663      *        built from the Map specified at this object creation.
       
   664      **/
       
   665     private void encodeStubInAddress(RMIServer rmiServer, Map attributes)
       
   666             throws IOException {
       
   667 
       
   668         final String protocol, host;
       
   669         final int port;
       
   670 
       
   671         if (address == null) {
       
   672             if (rmiServer instanceof javax.rmi.CORBA.Stub)
       
   673                 protocol = "iiop";
       
   674             else
       
   675                 protocol = "rmi";
       
   676             host = null; // will default to local host name
       
   677             port = 0;
       
   678         } else {
       
   679             protocol = address.getProtocol();
       
   680             host = (address.getHost().equals("")) ? null : address.getHost();
       
   681             port = address.getPort();
       
   682         }
       
   683 
       
   684         final String urlPath = encodeStub(rmiServer, attributes);
       
   685 
       
   686         address = new JMXServiceURL(protocol, host, port, urlPath);
       
   687     }
       
   688 
       
   689     static boolean isIiopURL(JMXServiceURL directoryURL, boolean strict)
       
   690         throws MalformedURLException {
       
   691         String protocol = directoryURL.getProtocol();
       
   692         if (protocol.equals("rmi"))
       
   693             return false;
       
   694         else if (protocol.equals("iiop"))
       
   695             return true;
       
   696         else if (strict) {
       
   697 
       
   698             throw new MalformedURLException("URL must have protocol " +
       
   699                                             "\"rmi\" or \"iiop\": \"" +
       
   700                                             protocol + "\"");
       
   701         }
       
   702         return false;
       
   703     }
       
   704 
       
   705     /**
       
   706      * Returns the IOR of the given rmiServer.
       
   707      **/
       
   708     static String encodeStub(RMIServer rmiServer, Map env) throws IOException {
       
   709         if (rmiServer instanceof javax.rmi.CORBA.Stub)
       
   710             return "/ior/" + encodeIIOPStub(rmiServer, env);
       
   711         else
       
   712             return "/stub/" + encodeJRMPStub(rmiServer, env);
       
   713     }
       
   714 
       
   715     static String encodeJRMPStub(RMIServer rmiServer, Map env)
       
   716             throws IOException {
       
   717         ByteArrayOutputStream bout = new ByteArrayOutputStream();
       
   718         ObjectOutputStream oout = new ObjectOutputStream(bout);
       
   719         oout.writeObject(rmiServer);
       
   720         oout.close();
       
   721         byte[] bytes = bout.toByteArray();
       
   722         return byteArrayToBase64(bytes);
       
   723     }
       
   724 
       
   725     static String encodeIIOPStub(RMIServer rmiServer, Map env)
       
   726             throws IOException {
       
   727         try {
       
   728             javax.rmi.CORBA.Stub stub =
       
   729                 (javax.rmi.CORBA.Stub) rmiServer;
       
   730             return stub._orb().object_to_string(stub);
       
   731         } catch (org.omg.CORBA.BAD_OPERATION x) {
       
   732             throw newIOException(x.getMessage(), x);
       
   733         }
       
   734     }
       
   735 
       
   736     /**
       
   737      * Object that we will bind to the registry.
       
   738      * This object is a stub connected to our RMIServerImpl.
       
   739      **/
       
   740     private static RMIServer objectToBind(RMIServerImpl rmiServer, Map env)
       
   741         throws IOException {
       
   742         return RMIConnector.
       
   743             connectStub((RMIServer)rmiServer.toStub(),env);
       
   744     }
       
   745 
       
   746     private static RMIServerImpl newJRMPServer(Map<String, ?> env, int port)
       
   747             throws IOException {
       
   748         RMIClientSocketFactory csf = (RMIClientSocketFactory)
       
   749             env.get(RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE);
       
   750         RMIServerSocketFactory ssf = (RMIServerSocketFactory)
       
   751             env.get(RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE);
       
   752         return new RMIJRMPServerImpl(port, csf, ssf, env);
       
   753     }
       
   754 
       
   755     private static RMIServerImpl newIIOPServer(Map<String, ?> env)
       
   756             throws IOException {
       
   757         return new RMIIIOPServerImpl(env);
       
   758     }
       
   759 
       
   760     private static String byteArrayToBase64(byte[] a) {
       
   761         int aLen = a.length;
       
   762         int numFullGroups = aLen/3;
       
   763         int numBytesInPartialGroup = aLen - 3*numFullGroups;
       
   764         int resultLen = 4*((aLen + 2)/3);
       
   765         final StringBuilder result = new StringBuilder(resultLen);
       
   766 
       
   767         // Translate all full groups from byte array elements to Base64
       
   768         int inCursor = 0;
       
   769         for (int i=0; i<numFullGroups; i++) {
       
   770             int byte0 = a[inCursor++] & 0xff;
       
   771             int byte1 = a[inCursor++] & 0xff;
       
   772             int byte2 = a[inCursor++] & 0xff;
       
   773             result.append(intToAlpha[byte0 >> 2]);
       
   774             result.append(intToAlpha[(byte0 << 4)&0x3f | (byte1 >> 4)]);
       
   775             result.append(intToAlpha[(byte1 << 2)&0x3f | (byte2 >> 6)]);
       
   776             result.append(intToAlpha[byte2 & 0x3f]);
       
   777         }
       
   778 
       
   779         // Translate partial group if present
       
   780         if (numBytesInPartialGroup != 0) {
       
   781             int byte0 = a[inCursor++] & 0xff;
       
   782             result.append(intToAlpha[byte0 >> 2]);
       
   783             if (numBytesInPartialGroup == 1) {
       
   784                 result.append(intToAlpha[(byte0 << 4) & 0x3f]);
       
   785                 result.append("==");
       
   786             } else {
       
   787                 // assert numBytesInPartialGroup == 2;
       
   788                 int byte1 = a[inCursor++] & 0xff;
       
   789                 result.append(intToAlpha[(byte0 << 4)&0x3f | (byte1 >> 4)]);
       
   790                 result.append(intToAlpha[(byte1 << 2)&0x3f]);
       
   791                 result.append('=');
       
   792             }
       
   793         }
       
   794         // assert inCursor == a.length;
       
   795         // assert result.length() == resultLen;
       
   796         return result.toString();
       
   797     }
       
   798 
       
   799     /**
       
   800      * This array is a lookup table that translates 6-bit positive integer
       
   801      * index values into their "Base64 Alphabet" equivalents as specified
       
   802      * in Table 1 of RFC 2045.
       
   803      */
       
   804     private static final char intToAlpha[] = {
       
   805         'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
       
   806         'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
       
   807         'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
       
   808         'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
       
   809         '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
       
   810     };
       
   811 
       
   812     /**
       
   813      * Construct a new IOException with a nested exception.
       
   814      * The nested exception is set only if JDK >= 1.4
       
   815      */
       
   816     private static IOException newIOException(String message,
       
   817                                               Throwable cause) {
       
   818         final IOException x = new IOException(message);
       
   819         return EnvHelp.initCause(x,cause);
       
   820     }
       
   821 
       
   822 
       
   823     // Private variables
       
   824     // -----------------
       
   825 
       
   826     private static ClassLogger logger =
       
   827         new ClassLogger("javax.management.remote.rmi", "RMIConnectorServer");
       
   828 
       
   829     private JMXServiceURL address;
       
   830     private RMIServerImpl rmiServerImpl;
       
   831     private final Map<String, ?> attributes;
       
   832     private ClassLoader defaultClassLoader = null;
       
   833 
       
   834     private String boundJndiUrl;
       
   835 
       
   836     // state
       
   837     private static final int CREATED = 0;
       
   838     private static final int STARTED = 1;
       
   839     private static final int STOPPED = 2;
       
   840 
       
   841     private int state = CREATED;
       
   842     private final static Set<RMIConnectorServer> openedServers =
       
   843             new HashSet<RMIConnectorServer>();
       
   844 }