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