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