/*
 * @(#)RMIConnectorServer.java	1.61 04/06/21
 * 
 * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

package javax.management.remote.rmi;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.net.MalformedURLException;
import java.rmi.Remote;
import java.rmi.server.RemoteObject;
import java.rmi.server.RMIClientSocketFactory;
import java.rmi.server.RMIServerSocketFactory;
import java.util.Collections;
import java.util.Hashtable;
import java.util.Map;
import java.util.HashMap;
import java.util.Set;
import java.util.HashSet;

import javax.naming.InitialContext;
import javax.naming.NamingException;

import javax.management.MBeanRegistration;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.management.InstanceNotFoundException;

import javax.management.remote.JMXConnectionNotification;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXServiceURL;
import javax.management.remote.MBeanServerForwarder;

import com.sun.jmx.remote.security.MBeanServerFileAccessController;
import com.sun.jmx.remote.util.ClassLogger;
import com.sun.jmx.remote.util.EnvHelp;

/**
 * <p>A JMX API connector server that creates RMI-based connections
 * from remote clients.  Usually, such connector servers are made
 * using {@link javax.management.remote.JMXConnectorServerFactory
 * JMXConnectorServerFactory}.  However, specialized applications can
 * use this class directly, for example with an {@link RMIServerImpl}
 * object.</p>
 *
 * @since 1.5
 * @since.unbundled 1.0
 */
public class RMIConnectorServer extends JMXConnectorServer {
    /**
     * <p>Name of the attribute that specifies whether the {@link
     * RMIServer} stub that represents an RMI connector server should
     * override an existing stub at the same address.  The value
     * associated with this attribute, if any, should be a string that
     * is equal, ignoring case, to <code>"true"</code> or
     * <code>"false"</code>.  The default value is false.</p>
     */
    public static final String JNDI_REBIND_ATTRIBUTE =
        "jmx.remote.jndi.rebind";

    /**
     * <p>Name of the attribute that specifies the {@link
     * RMIClientSocketFactory} for the RMI objects created in
     * conjunction with this connector. The value associated with this
     * attribute must be of type <code>RMIClientSocketFactory</code> and can
     * only be specified in the <code>Map</code> argument supplied when
     * creating a connector server.</p>
     */
    public static final String RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE =
        "jmx.remote.rmi.client.socket.factory";

    /**
     * <p>Name of the attribute that specifies the {@link
     * RMIServerSocketFactory} for the RMI objects created in
     * conjunction with this connector. The value associated with this
     * attribute must be of type <code>RMIServerSocketFactory</code> and can
     * only be specified in the <code>Map</code> argument supplied when
     * creating a connector server.</p>
     */
    public static final String RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE =
        "jmx.remote.rmi.server.socket.factory";

    /**
     * <p>Makes an <code>RMIConnectorServer</code>.
     * This is equivalent to calling {@link #RMIConnectorServer(
     * JMXServiceURL,Map,RMIServerImpl,MBeanServer) 
     * RMIConnectorServer(directoryURL,environment,null,null)}</p>
     *
     * @param url the URL defining how to create the connector server.
     * Cannot be null.
     *
     * @param environment attributes governing the creation and
     * storing of the RMI object.  Can be null, which is equivalent to
     * an empty Map.
     *
     * @exception IllegalArgumentException if <code>url</code> is null.
     *
     * @exception MalformedURLException if <code>url</code> does not
     * conform to the syntax for an RMI connector, or if its protocol
     * is not recognized by this implementation. Only "rmi" and "jrmp"
     * are valid when this constructor is used.
     *
     * @exception IOException if the connector server cannot be created
     * for some reason or if it is inevitable that its {@link #start()
     * start} method will fail.
     */
    public RMIConnectorServer(JMXServiceURL url, Map<String,?> environment)
            throws IOException {
        this(url, environment, (MBeanServer) null);
    }

    /**
     * <p>Makes an <code>RMIConnectorServer</code> for the given MBean
     * server.
     * This is equivalent to calling {@link #RMIConnectorServer(
     * JMXServiceURL,Map,RMIServerImpl,MBeanServer) 
     * RMIConnectorServer(directoryURL,environment,null,mbeanServer)}</p>
     *
     * @param url the URL defining how to create the connector server.
     * Cannot be null.
     *
     * @param environment attributes governing the creation and
     * storing of the RMI object.  Can be null, which is equivalent to
     * an empty Map.
     *
     * @param mbeanServer the MBean server to which the new connector
     * server is attached, or null if it will be attached by being
     * registered as an MBean in the MBean server.
     *
     * @exception IllegalArgumentException if <code>url</code> is null.
     *
     * @exception MalformedURLException if <code>url</code> does not
     * conform to the syntax for an RMI connector, or if its protocol
     * is not recognized by this implementation. Only "rmi" and "jrmp"
     * are valid when this constructor is used.
     *
     * @exception IOException if the connector server cannot be created
     * for some reason or if it is inevitable that its {@link #start()
     * start} method will fail.
     */
    public RMIConnectorServer(JMXServiceURL url, Map<String,?> environment,
                              MBeanServer mbeanServer)
            throws IOException {
        this(url, environment, (RMIServerImpl) null, mbeanServer);
    }

    /**
     * <p>Makes an <code>RMIConnectorServer</code> for the given MBean
     * server.</p>
     *
     * @param url the URL defining how to create the connector server.
     * Cannot be null.
     *
     * @param environment attributes governing the creation and
     * storing of the RMI object.  Can be null, which is equivalent to
     * an empty Map.
     *
     * @param rmiServerImpl An implementation of the RMIServer interface,
     *  consistent with the protocol type specified in <var>url</var>.
     *  If this parameter is non null, the protocol type specified by
     *  <var>url</var> is not constrained, and is assumed to be valid. 
     *  Otherwise, only "rmi" and "iiop" will be recognized. 
     *
     * @param mbeanServer the MBean server to which the new connector
     * server is attached, or null if it will be attached by being
     * registered as an MBean in the MBean server.
     *
     * @exception IllegalArgumentException if <code>url</code> is null.
     *
     * @exception MalformedURLException if <code>url</code> does not
     * conform to the syntax for an RMI connector, or if its protocol
     * is not recognized by this implementation. Only "rmi" and "jrmp"
     * are recognized when <var>rmiServerImpl</var> is null.
     *
     * @exception IOException if the connector server cannot be created
     * for some reason or if it is inevitable that its {@link #start()
     * start} method will fail.
     *
     * @see #start
     */
    public RMIConnectorServer(JMXServiceURL url, Map<String,?> environment,
                              RMIServerImpl rmiServerImpl,
                              MBeanServer mbeanServer)
	    throws IOException {
	super(mbeanServer);

	if (url == null) throw new 
	    IllegalArgumentException("Null JMXServiceURL");
	if (rmiServerImpl == null) {
	    final String prt = url.getProtocol();
	    if (prt == null || !(prt.equals("rmi") || prt.equals("iiop"))) { 
		final String msg = "Invalid protocol type: " + prt;
		throw new MalformedURLException(msg);
	    }
	    final String urlPath = url.getURLPath();
	    if (!urlPath.equals("")
		&& !urlPath.equals("/")
		&& !urlPath.startsWith("/jndi/")) {
		final String msg = "URL path must be empty or start with " +
		    "/jndi/";
		throw new MalformedURLException(msg);
	    }
	}

        if (environment == null)
            this.attributes = Collections.EMPTY_MAP;
        else {
	    EnvHelp.checkAttributes(environment);
            this.attributes = Collections.unmodifiableMap(environment);
	}

        this.address = url;
        this.rmiServerImpl = rmiServerImpl;
    }

    /**
     * <p>Returns a client stub for this connector server.  A client
     * stub is a serializable object whose {@link
     * JMXConnector#connect(Map) connect} method can be used to make
     * one new connection to this connector server.</p>
     *
     * @param env client connection parameters of the same sort that
     * could be provided to {@link JMXConnector#connect(Map)
     * JMXConnector.connect(Map)}.  Can be null, which is equivalent
     * to an empty map.
     *
     * @return a client stub that can be used to make a new connection
     * to this connector server.
     *
     * @exception UnsupportedOperationException if this connector
     * server does not support the generation of client stubs.
     *
     * @exception IllegalStateException if the JMXConnectorServer is
     * not started (see {@link #isActive()}).
     *
     * @exception IOException if a communications problem means that a
     * stub cannot be created.
     **/
    public JMXConnector toJMXConnector(Map<String,?> env) throws IOException {
        // The serialized for of rmiServerImpl is automagically
        // a RMI server stub.
        if (!isActive()) throw new 
            IllegalStateException("Connector is not active");

        // Merge maps
        Map usemap = new
	    HashMap((this.attributes==null)?Collections.EMPTY_MAP:
		    this.attributes);

        if (env != null) {
	    EnvHelp.checkAttributes(env);
            usemap.putAll(env);
        }

	usemap = EnvHelp.filterAttributes(usemap);

        final RMIServer stub=(RMIServer)rmiServerImpl.toStub();

        return new RMIConnector(stub, usemap);
    }

    /**
     * <p>Activates the connector server, that is starts listening for
     * client connections.  Calling this method when the connector
     * server is already active has no effect.  Calling this method
     * when the connector server has been stopped will generate an
     * <code>IOException</code>.</p>
     *
     * <p>The behaviour of this method when called for the first time
     * depends on the parameters that were supplied at construction,
     * as described below.</p>
     *
     * <p>First, an object of a subclass of {@link RMIServerImpl} is
     * required, to export the connector server through RMI:</p>
     *
     * <ul>
     *
     * <li>If an <code>RMIServerImpl</code> was supplied to the
     * constructor, it is used.
     *
     * <li>Otherwise, if the protocol part of the
     * <code>JMXServiceURL</code> supplied to the constructor was
     * <code>iiop</code>, an object of type {@link RMIIIOPServerImpl}
     * is created.
     *
     * <li>Otherwise, if the <code>JMXServiceURL</code>
     * was null, or its protocol part was <code>rmi</code>, an object
     * of type {@link RMIJRMPServerImpl} is created.
     *
     * <li>Otherwise, the implementation can create an
     * implementation-specific {@link RMIServerImpl} or it can throw
     * {@link MalformedURLException}.
     *
     * </ul>
     *
     * <p>If the given address includes a JNDI directory URL as
     * specified in the package documentation for {@link
     * javax.management.remote.rmi}, then this
     * <code>RMIConnectorServer</code> will bootstrap by binding the
     * <code>RMIServerImpl</code> to the given address.</p>
     *
     * <p>If the URL path part of the <code>JMXServiceURL</code> was
     * empty or a single slash (<code>/</code>), then the RMI object
     * will not be bound to a directory.  Instead, a reference to it
     * will be encoded in the URL path of the RMIConnectorServer
     * address (returned by {@link #getAddress()}).  The encodings for
     * <code>rmi</code> and <code>iiop</code> are described in the
     * package documentation for {@link
     * javax.management.remote.rmi}.</p>
     *
     * <p>The behavior when the URL path is neither empty nor a JNDI
     * directory URL, or when the protocol is neither <code>rmi</code>
     * nor <code>iiop</code>, is implementation defined, and may
     * include throwing {@link MalformedURLException} when the
     * connector server is created or when it is started.</p>
     *
     * @exception IllegalStateException if the connector server has
     * not been attached to an MBean server.
     * @exception IOException if the connector server cannot be
     * started.
     */
    public synchronized void start() throws IOException {
	final boolean tracing = logger.traceOn();

	if (state == STARTED) {
	    if (tracing) logger.trace("start", "already started");
	    return;
	} else if (state == STOPPED) {
	    if (tracing) logger.trace("start", "already stopped");
	    throw new IOException("The server has been stopped.");
	}

        MBeanServer mbs = getMBeanServer();
        if (mbs == null)
	    throw new IllegalStateException("This connector server is not " +
					    "attached to an MBean server");

	// Check the internal access file property to see
	// if an MBeanServerForwarder is to be provided
	//
	if (attributes != null) {
	    // Check if access file property is specified
	    //
	    String accessFile =
		(String) attributes.get("jmx.remote.x.access.file");
	    if (accessFile != null) {
		// Access file property specified, create an instance
		// of the MBeanServerFileAccessController class
		//
		MBeanServerForwarder mbsf = null;
		try {
		    mbsf = new MBeanServerFileAccessController(accessFile);
		} catch (IOException e) {
		    throw (IllegalArgumentException)
			EnvHelp.initCause(
			   new IllegalArgumentException(e.getMessage()), e);
		}
		// Set the MBeanServerForwarder
		//
		setMBeanServerForwarder(mbsf);
		mbs = getMBeanServer();
	    }
	}

        try {
	    if (tracing) logger.trace("start", "setting default class loader");
            defaultClassLoader = EnvHelp.resolveServerClassLoader(attributes, mbs);
        } catch (InstanceNotFoundException infc) {
            IllegalArgumentException x = new 
                IllegalArgumentException("ClassLoader not found: "+infc);
            throw (IllegalArgumentException)EnvHelp.initCause(x,infc);
        }

	if (tracing) logger.trace("start", "setting RMIServer object");
        final RMIServerImpl rmiServer;

	if (rmiServerImpl != null)
	    rmiServer = rmiServerImpl;
	else
            rmiServer = newServer();

        rmiServer.setMBeanServer(mbs);
        rmiServer.setDefaultClassLoader(defaultClassLoader);
	rmiServer.setRMIConnectorServer(this);
	rmiServer.export();
        
        try {
            if (tracing) logger.trace("start", "getting RMIServer object to export");
            final RMIServer objref = objectToBind(rmiServer, attributes);

            if (address != null && address.getURLPath().startsWith("/jndi/")) {
                final String jndiUrl = address.getURLPath().substring(6);

                if (tracing)
                    logger.trace("start", "Using external directory: " + jndiUrl);

                final boolean rebind;

                String rebindS = (String)
                    attributes.get(JNDI_REBIND_ATTRIBUTE);
                if (rebindS == null)                        rebind = false;
                else if (rebindS.equalsIgnoreCase("true"))  rebind = true;
                else if (rebindS.equalsIgnoreCase("false")) rebind = false;
                else throw new
                    IllegalArgumentException(JNDI_REBIND_ATTRIBUTE + "must" +
                                             " be \"true\" or \"false\"");

                if (tracing)
                    logger.trace("start", JNDI_REBIND_ATTRIBUTE + "=" + rebind);

                try {
                    if (tracing) logger.trace("start", "binding to " + jndiUrl);

                    final Hashtable usemap = EnvHelp.mapToHashtable(attributes);
                    final boolean isIiop = isIiopURL(address, true);
                    if (isIiop) {
                        // Make sure java.naming.corba.orb is in the Map.
                        usemap.put(EnvHelp.DEFAULT_ORB,
                                   RMIConnector.resolveOrb(attributes));
                    }

                    bind(jndiUrl, usemap, objref, rebind);
                    boundJndiUrl = jndiUrl;
                } catch (NamingException e) {
                    // fit e in the nested exception if we are on 1.4
                    throw newIOException("Cannot bind to URL ["+jndiUrl+"]: "
                                         + e, e);
                }
            } else {
                // if jndiURL is null, we must encode the stub into the URL.
                if (tracing) logger.trace("start", "Encoding URL");

                encodeStubInAddress(objref, attributes);

                if (tracing) logger.trace("start", "Encoded URL: " + this.address);
            }
        } catch (Exception e) {
	    try {
		rmiServer.close();
	    } catch (Exception x) {
		// OK: we are already throwing another exception
	    }
	    if (e instanceof RuntimeException)
		throw (RuntimeException) e;
	    else if (e instanceof IOException)
		throw (IOException) e;
	    else
		throw newIOException("Got unexpected exception while " +
				     "starting the connector server: "
				     + e, e);
        }

        rmiServerImpl = rmiServer;

	synchronized(openedServers) {
	    openedServers.add(this);
	}

        state = STARTED;

        if (tracing) {
            logger.trace("start", "Connector Server Address = " + address);
            logger.trace("start", "started.");
        }
    }

    /**
     * <p>Deactivates the connector server, that is, stops listening for
     * client connections.  Calling this method will also close all
     * client connections that were made by this server.  After this
     * method returns, whether normally or with an exception, the
     * connector server will not create any new client
     * connections.</p>
     *
     * <p>Once a connector server has been stopped, it cannot be started
     * again.</p>
     *
     * <p>Calling this method when the connector server has already
     * been stopped has no effect.  Calling this method when the
     * connector server has not yet been started will disable the
     * connector server object permanently.</p>
     *
     * <p>If closing a client connection produces an exception, that
     * exception is not thrown from this method.  A {@link
     * JMXConnectionNotification} is emitted from this MBean with the
     * connection ID of the connection that could not be closed.</p>
     *
     * <p>Closing a connector server is a potentially slow operation.
     * For example, if a client machine with an open connection has
     * crashed, the close operation might have to wait for a network
     * protocol timeout.  Callers that do not want to block in a close
     * operation should do it in a separate thread.</p>
     *
     * <p>This method calls the method {@link RMIServerImpl#close()
     * close} on the connector server's <code>RMIServerImpl</code>
     * object.</p>
     *
     * <p>If the <code>RMIServerImpl</code> was bound to a JNDI
     * directory by the {@link #start() start} method, it is unbound
     * from the directory by this method.</p>
     *
     * @exception IOException if the server cannot be closed cleanly,
     * or if the <code>RMIServerImpl</code> cannot be unbound from the
     * directory.  When this exception is thrown, the server has
     * already attempted to close all client connections, if
     * appropriate; to call {@link RMIServerImpl#close()}; and to
     * unbind the <code>RMIServerImpl</code> from its directory, if
     * appropriate.  All client connections are closed except possibly
     * those that generated exceptions when the server attempted to
     * close them.
     */
    public void stop() throws IOException {
	final boolean tracing = logger.traceOn();

	synchronized (this) {
	    if (state == STOPPED) {
		if (tracing) logger.trace("stop","already stopped.");
		return;
	    } else if (state == CREATED) {
		if (tracing) logger.trace("stop","not started yet.");
	    }

	    if (tracing) logger.trace("stop", "stopping.");
	    state = STOPPED;
	}

	synchronized(openedServers) {
	    openedServers.remove(this);
	}

        IOException exception = null;

	// rmiServerImpl can be null if stop() called without start()
	if (rmiServerImpl != null) {
	    try {
		if (tracing) logger.trace("stop", "closing RMI server.");
		rmiServerImpl.close();
	    } catch (IOException e) {
		if (tracing) logger.trace("stop", "failed to close RMI server: " + e);
		if (logger.debugOn()) logger.debug("stop",e);
		exception = e;
	    }
	}

        if (boundJndiUrl != null) {
            try {
		if (tracing) 
		    logger.trace("stop",
			  "unbind from external directory: " + boundJndiUrl);
		
		final Hashtable usemap = EnvHelp.mapToHashtable(attributes);
		final boolean isIiop = isIiopURL(address, true);
		if (isIiop) {
		    // Make sure java.naming.corba.orb is in the Map.
		    usemap.put(EnvHelp.DEFAULT_ORB, 
			       RMIConnector.resolveOrb(attributes));
		}
                InitialContext ctx = 
                    new InitialContext(usemap);
                ctx.unbind(boundJndiUrl);
                ctx.close();
            } catch (NamingException e) {
		if (tracing) logger.trace("stop", "failed to unbind RMI server: "+e);
		if (logger.debugOn()) logger.debug("stop",e);
                // fit e in as the nested exception if we are on 1.4
                if (exception == null)
                    exception = newIOException("Cannot bind to URL: " + e, e);
            }
        }

        if (exception != null) throw exception;

	if (tracing) logger.trace("stop", "stopped");
    }

    public synchronized boolean isActive() {
	return (state == STARTED);
    }

    public JMXServiceURL getAddress() {
        if (!isActive())
	    return null;
        return address;
    }

    public Map<String,?> getAttributes() {
	Map map = EnvHelp.filterAttributes(attributes);
        return Collections.unmodifiableMap(map);
    }

    /* We repeat the definitions of connection{Opened,Closed,Failed}
       here so that they are accessible to other classes in this package
       even though they have protected access.  */

    protected void connectionOpened(String connectionId, String message,
				    Object userData) {
	super.connectionOpened(connectionId, message, userData);
    }

    protected void connectionClosed(String connectionId, String message,
				    Object userData) {
	super.connectionClosed(connectionId, message, userData);
    }

    protected void connectionFailed(String connectionId, String message,
				    Object userData) {
	super.connectionFailed(connectionId, message, userData);
    }

    /**
     * Bind a stub to a registry.
     * @param jndiUrl URL of the stub in the registry, extracted 
     *        from the <code>JMXServiceURL</code>.
     * @param attributes A Hashtable containing environment parameters,
     *        built from the Map specified at this object creation.
     * @param rmiServer The object to bind in the registry
     * @param rebind true if the object must be rebound.
     **/
    void bind(String jndiUrl, Hashtable attributes, 
              RMIServer rmiServer, boolean rebind) 
        throws NamingException, MalformedURLException {
        // if jndiURL is not null, we nust bind the stub to a 
        // directory.
        InitialContext ctx = 
            new InitialContext(attributes);
        
        if (rebind)
            ctx.rebind(jndiUrl, rmiServer);
        else
            ctx.bind(jndiUrl, rmiServer);
        ctx.close();
    }

    /**
     * Creates a new RMIServerImpl.
     **/
    RMIServerImpl newServer() throws IOException {
        final boolean iiop = isIiopURL(address,true);
	final int port;
	if (address == null)
	    port = 0;
	else
	    port = address.getPort();
        if (iiop)
            return newIIOPServer(attributes);
        else
            return newJRMPServer(attributes, port);
    }
    
    /**
     * Encode a stub into the JMXServiceURL.
     * @param rmiServer The stub object to encode in the URL
     * @param attributes A Map containing environment parameters,
     *        built from the Map specified at this object creation.
     **/
    private void encodeStubInAddress(RMIServer rmiServer, Map attributes)
	    throws IOException {

	final String protocol, host;
	final int port;

	if (address == null) {
	    if (rmiServer instanceof javax.rmi.CORBA.Stub)
		protocol = "iiop";
	    else
		protocol = "rmi";
	    host = null; // will default to local host name
	    port = 0;
	} else {
	    protocol = address.getProtocol();
	    host = (address.getHost().equals("")) ? null : address.getHost();
	    port = address.getPort();
	}

        final String urlPath = encodeStub(rmiServer, attributes);
        
        address = new JMXServiceURL(protocol, host, port, urlPath);
    }

    static boolean isIiopURL(JMXServiceURL directoryURL, boolean strict)
	throws MalformedURLException {
        String protocol = directoryURL.getProtocol();
        if (protocol.equals("rmi"))
            return false;
        else if (protocol.equals("iiop"))
            return true;
        else if (strict) {
	    
            throw new MalformedURLException("URL must have protocol " +
                                            "\"rmi\" or \"iiop\": \"" +
					    protocol + "\"");
        }
	return false;
    }
    
    /**
     * Returns the IOR of the given rmiServer.
     **/
    static String encodeStub(RMIServer rmiServer, Map env) throws IOException {
        if (rmiServer instanceof javax.rmi.CORBA.Stub)
	    return "/ior/" + encodeIIOPStub(rmiServer, env);
	else
	    return "/stub/" + encodeJRMPStub(rmiServer, env);
    }

    static String encodeJRMPStub(RMIServer rmiServer, Map env)
	    throws IOException {
	ByteArrayOutputStream bout = new ByteArrayOutputStream();
	ObjectOutputStream oout = new ObjectOutputStream(bout);
	oout.writeObject(rmiServer);
	oout.close();
	byte[] bytes = bout.toByteArray();
	return byteArrayToBase64(bytes);
    }

    static String encodeIIOPStub(RMIServer rmiServer, Map env)
	    throws IOException {
	try {
	    javax.rmi.CORBA.Stub stub = 
		(javax.rmi.CORBA.Stub) rmiServer;
	    return stub._orb().object_to_string(stub);
	} catch (org.omg.CORBA.BAD_OPERATION x) {
	    throw newIOException(x.getMessage(), x);
	}
    }

    /**
     * Object that we will bind to the registry.
     * This object is a stub connected to our RMIServerImpl.
     **/
    private static RMIServer objectToBind(RMIServerImpl rmiServer, Map env) 
        throws IOException {
        return RMIConnector.
            connectStub((RMIServer)rmiServer.toStub(),env);
    }

    private static RMIServerImpl newJRMPServer(Map env, int port)
	    throws IOException {
        RMIClientSocketFactory csf = (RMIClientSocketFactory)
            env.get(RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE);
        RMIServerSocketFactory ssf = (RMIServerSocketFactory)
            env.get(RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE);
        return new RMIJRMPServerImpl(port, csf, ssf, env);
    }

    private static RMIServerImpl newIIOPServer(Map env)
	    throws IOException {
        return new RMIIIOPServerImpl(env);
    }

    private static String byteArrayToBase64(byte[] a) {
        int aLen = a.length;
        int numFullGroups = aLen/3;
        int numBytesInPartialGroup = aLen - 3*numFullGroups;
        int resultLen = 4*((aLen + 2)/3);
        StringBuffer result = new StringBuffer(resultLen);

        // Translate all full groups from byte array elements to Base64
        int inCursor = 0;
        for (int i=0; i<numFullGroups; i++) {
            int byte0 = a[inCursor++] & 0xff;
            int byte1 = a[inCursor++] & 0xff;
            int byte2 = a[inCursor++] & 0xff;
            result.append(intToAlpha[byte0 >> 2]);
            result.append(intToAlpha[(byte0 << 4)&0x3f | (byte1 >> 4)]);
            result.append(intToAlpha[(byte1 << 2)&0x3f | (byte2 >> 6)]);
            result.append(intToAlpha[byte2 & 0x3f]);
        }

        // Translate partial group if present
        if (numBytesInPartialGroup != 0) {
            int byte0 = a[inCursor++] & 0xff;
            result.append(intToAlpha[byte0 >> 2]);
            if (numBytesInPartialGroup == 1) {
                result.append(intToAlpha[(byte0 << 4) & 0x3f]);
                result.append("==");
            } else {
                // assert numBytesInPartialGroup == 2;
                int byte1 = a[inCursor++] & 0xff;
                result.append(intToAlpha[(byte0 << 4)&0x3f | (byte1 >> 4)]);
                result.append(intToAlpha[(byte1 << 2)&0x3f]);
                result.append('=');
            }
        }
        // assert inCursor == a.length;
        // assert result.length() == resultLen;
        return result.toString();
    }
 
    /**
     * This array is a lookup table that translates 6-bit positive integer
     * index values into their "Base64 Alphabet" equivalents as specified 
     * in Table 1 of RFC 2045.
     */
    private static final char intToAlpha[] = {
        'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
        'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
        'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
        'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
    };

    /**
     * Construct a new IOException with a nested exception.  
     * The nested exception is set only if JDK >= 1.4
     */
    private static IOException newIOException(String message, 
                                              Throwable cause) {
        final IOException x = new IOException(message);
        return (IOException)EnvHelp.initCause(x,cause);
    }


    // Private variables
    // -----------------

    private static ClassLogger logger =
	new ClassLogger("javax.management.remote.rmi", "RMIConnectorServer");

    private JMXServiceURL address;
    private RMIServerImpl rmiServerImpl;
    private final Map attributes;
    private ClassLoader defaultClassLoader = null;

    private String boundJndiUrl;

    // state 
    private static final int CREATED = 0;
    private static final int STARTED = 1;
    private static final int STOPPED = 2;

    private int state = CREATED;
    private final static Set openedServers = new HashSet();
}
