/*
 * 
 * Copyright 2004 Sun Microsystems, Inc. All Rights Reserved.
 * 
 * The contents of this file are subject to the Sun Community Source
 * License v3.0/Jini Technology Specific Attachment v 1.0 (the
 * "License"). You may not use this file except in compliance with the
 * License. You may obtain a copy of the License at
 * http://wwws.sun.com/software/jini/licensing/SCSL3_JiniTSA1.html.
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 * 
 * The Reference Code is Jini Technology Core Platform code, v2.0. The
 * Developer of the Reference Code is Sun Microsystems, Inc.
 * 
 * Contributor(s): Sun Microsystems, Inc.
 * 
 * The contents of this file comply with the Jini Technology Core
 * Platform Compatibility Kit, v2.0A.
 * 
 * Tester(s): Sun Microsystems, Inc.
 * 
 * Test Platform(s):
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Solaris(TM)SPARC/x86
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Linux (Intel x86)
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Windows (Intel Platform)
 * 
 */

package net.jini.jeri.tcp;

import com.sun.jini.action.GetBooleanAction;
import com.sun.jini.jeri.internal.runtime.Util;
import com.sun.jini.logging.Levels;
import com.sun.jini.thread.Executor;
import com.sun.jini.thread.GetThreadPoolAction;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.nio.channels.SocketChannel;
import java.nio.channels.ServerSocketChannel;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import net.jini.core.constraint.InvocationConstraints;
import net.jini.io.context.ClientHost;
import net.jini.io.UnsupportedConstraintException;
import net.jini.jeri.Endpoint;
import net.jini.jeri.InboundRequest;
import net.jini.jeri.RequestDispatcher;
import net.jini.jeri.ServerEndpoint;
import net.jini.jeri.connection.InboundRequestHandle;
import net.jini.jeri.connection.ServerConnection;
import net.jini.jeri.connection.ServerConnectionManager;
import net.jini.security.Security;
import net.jini.security.SecurityContext;

/**
 * An implementation of the {@link ServerEndpoint} abstraction that
 * uses TCP sockets (instances of {@link ServerSocket}) for the
 * underlying communication mechanism.
 *
 * <p><code>TcpServerEndpoint</code> instances contain a host name and
 * a TCP port number, as well as an optional {@link
 * ServerSocketFactory} for customizing the type of
 * <code>ServerSocket</code> to use and an optional {@link
 * SocketFactory} for customizing the type of {@link Socket} that
 * client endpoints will use.  The port number is the local TCP port
 * to bind to when listening for incoming socket connections.  If the
 * port number is zero, then each listen operation will bind to a free
 * (non-zero) port, which will be the port number contained in the
 * resulting {@link TcpEndpoint}.  The host name contained in a
 * <code>TcpServerEndpoint</code> controls the host name that will be
 * contained in the <code>TcpEndpoint</code> instances produced when
 * {@link #enumerateListenEndpoints enumerateListenEndpoints} is
 * invoked to listen on the <code>TcpServerEndpoint</code> (this host
 * name does not affect the behavior of listen operations themselves).
 * If the host name in a <code>TcpServerEndpoint</code> is
 * <code>null</code>, then the host name in the
 * <code>TcpEndpoint</code> instances that it produces will be the IP
 * address string obtained from {@link InetAddress#getLocalHost
 * InetAddress.getLocalHost} when {@link #enumerateListenEndpoints
 * enumerateListenEndpoints} is invoked.
 *
 * <p><code>TcpServerEndpoint</code> uses the <a
 * href="../connection/doc-files/mux.html">Jini(TM) extensible remote
 * invocation (Jini ERI) multiplexing protocol</a> to map incoming
 * requests to socket connections.
 *
 * <p>A <code>ServerSocketFactory</code> used with a
 * <code>TcpServerEndpoint</code> must implement {@link Object#equals
 * Object.equals} to obey the guidelines that are specified for
 * <code>equals</code> methods of {@link
 * net.jini.jeri.ServerEndpoint.ListenEndpoint ListenEndpoint}
 * instances.  A <code>SocketFactory</code> used with a
 * <code>TcpServerEndpoint</code> should be serializable and must
 * implement <code>Object.equals</code> to obey the guidelines that
 * are specified for <code>equals</code> methods of {@link Endpoint}
 * instances.
 *
 * @author Sun Microsystems, Inc.
 * @see TcpEndpoint
 * @since 2.0
 **/
public final class TcpServerEndpoint implements ServerEndpoint {

    /**
     * pool of threads for executing tasks in system thread group:
     * used for TCP accept threads
     **/
    private static final Executor systemThreadPool =
	(Executor) AccessController.doPrivileged(
	    new GetThreadPoolAction(false));

    private static final ServerConnectionManager serverConnectionManager =
	new ServerConnectionManager();

    /** server transport logger */
    private static final Logger logger =
	Logger.getLogger("net.jini.jeri.tcp.server");

    /** whether or not to use NIO-based sockets if possible */
    private static final boolean useNIO =		// default false
	((Boolean) AccessController.doPrivileged(new GetBooleanAction(
	    "com.sun.jini.jeri.tcp.useNIO"))).booleanValue();

    /** name for local host to fill in to corresponding TcpEndpoints */
    private final String host;

    /** the TCP port that this TcpServerEndpoint listens on */
    private final int port;

    /**
     * the socket factory that TcpEndpoint objects produced by
     * listening on this TcpServerEndpoint will use to create Socket
     * objects
     **/
    private final SocketFactory sf;

    /**
     * the socket factory that this TcpServerEndpoint uses to create
     * ServerSocket objects
     **/
    private final ServerSocketFactory ssf;

    /**
     * Returns a <code>TcpServerEndpoint</code> instance for the given
     * TCP port number.
     *
     * <p>The host name contained in the returned
     * <code>TcpServerEndpoint</code> will be <code>null</code>, so
     * that when its {@link #enumerateListenEndpoints
     * enumerateListenEndpoints} method produces a {@link
     * TcpEndpoint}, the <code>TcpEndpoint</code>'s host name will be
     * the IP address string obtained from {@link
     * InetAddress#getLocalHost InetAddress.getLocalHost}.
     *
     * <p>The <code>ServerSocketFactory</code> contained in the
     * returned <code>TcpServerEndpoint</code> will be
     * <code>null</code>, indicating that this endpoint will create
     * <code>ServerSocket</code> objects directly.  The
     * <code>SocketFactory</code> contained in the returned
     * <code>TcpServerEndpoint</code> will also be <code>null</code>.
     *
     * @param port the TCP port on the local host to listen on
     *
     * @return a <code>TcpServerEndpoint</code> instance
     *
     * @throws IllegalArgumentException if the port number is out of
     * the range <code>0</code> to <code>65535</code> (inclusive)
     **/
    public static TcpServerEndpoint getInstance(int port) {
	return getInstance(null, port, null, null);
    }

    /**
     * Returns a <code>TcpServerEndpoint</code> instance for the given
     * host name and TCP port number.
     *
     * <p>If <code>host</code> is <code>null</code>, then when the
     * returned <code>TcpServerEndpoint</code>'s {@link
     * #enumerateListenEndpoints enumerateListenEndpoints} method
     * produces a {@link TcpEndpoint}, the <code>TcpEndpoint</code>'s
     * host name will be the IP address string obtained from {@link
     * InetAddress#getLocalHost InetAddress.getLocalHost}.
     *
     * <p>The <code>ServerSocketFactory</code> contained in the
     * returned <code>TcpServerEndpoint</code> will be
     * <code>null</code>, indicating that this endpoint will create
     * <code>ServerSocket</code> objects directly.  The
     * <code>SocketFactory</code> contained in the returned
     * <code>TcpServerEndpoint</code> will also be <code>null</code>.
     *
     * @param host the host name to be used in
     * <code>TcpEndpoint</code> instances produced by listening on the
     * returned <code>TcpServerEndpoint</code>, or <code>null</code>
     *
     * @param port the TCP port on the local host to listen on
     *
     * @return a <code>TcpServerEndpoint</code> instance
     *
     * @throws IllegalArgumentException if the port number is out of
     * the range <code>0</code> to <code>65535</code> (inclusive)
     **/
    public static TcpServerEndpoint getInstance(String host, int port) {
	return getInstance(host, port, null, null);
    }

    /**
     * Returns a <code>TcpServerEndpoint</code> instance for the given
     * host name and TCP port number that contains the given
     * <code>SocketFactory</code> and
     * <code>ServerSocketFactory</code>.
     *
     * <p>If <code>host</code> is <code>null</code>, then when the
     * returned <code>TcpServerEndpoint</code>'s {@link
     * #enumerateListenEndpoints enumerateListenEndpoints} method
     * produces a {@link TcpEndpoint}, the <code>TcpEndpoint</code>'s
     * host name will be the IP address string obtained from {@link
     * InetAddress#getLocalHost InetAddress.getLocalHost}.
     *
     * <p>If the server socket factory argument is <code>null</code>,
     * then this endpoint will create <code>ServerSocket</code>
     * objects directly.
     *
     * @param host the host name to be used in
     * <code>TcpEndpoint</code> instances produced by listening on the
     * returned <code>TcpServerEndpoint</code>, or <code>null</code>
     *
     * @param port the TCP port on the local host to listen on
     *
     * @param sf the <code>SocketFactory</code> to use for this
     * <code>TcpServerEndpoint</code>, or <code>null</code>
     *
     * @param ssf the <code>ServerSocketFactory</code> to use for this
     * <code>TcpServerEndpoint</code>, or <code>null</code>
     *
     * @return a <code>TcpServerEndpoint</code> instance
     *
     * @throws IllegalArgumentException if the port number is out of
     * the range <code>0</code> to <code>65535</code> (inclusive)
     **/
    public static TcpServerEndpoint getInstance(String host, int port,
						SocketFactory sf,
						ServerSocketFactory ssf)
    {
	return new TcpServerEndpoint(host, port, sf, ssf);
    }

    /**
     * Constructs a new instance.
     **/
    private TcpServerEndpoint(String host, int port,
			      SocketFactory sf,
			      ServerSocketFactory ssf)
    {
	if (port < 0 || port > 0xFFFF) {
	    throw new IllegalArgumentException(
		"port number out of range: " + port);
	}
	this.host = host;
	this.port = port;
	this.sf = sf;
	this.ssf = ssf;
    }

    /**
     * Returns the host name that will be used in
     * <code>TcpEndpoint</code> instances produced by listening on
     * this <code>TcpServerEndpoint</code>, or <code>null</code> if
     * the IP address string obtained from {@link
     * InetAddress#getLocalHost InetAddress.getLocalHost} will be
     * used.
     *
     * @return the host name to use in <code>TcpEndpoint</code>
     * instances produced from this object, or <code>null</code>
     **/
    public String getHost() {
	return host;
    }

    /**
     * Returns the TCP port that this <code>TcpServerEndpoint</code>
     * listens on.
     *
     * @return the TCP port that this endpoint listens on
     **/
    public int getPort() {
	return port;
    }

    /**
     * Returns the <code>SocketFactory</code> that
     * <code>TcpEndpoint</code> objects produced by listening on this
     * <code>TcpServerEndpoint</code> will use to create
     * <code>Socket</code> objects.
     *
     * @return the socket factory that client-side endpoints
     * corresponding to this server endpoint will use to create
     * sockets, or <code>null</code> if no factory will be used
     **/
    public SocketFactory getSocketFactory() {
	return sf;
    }

    /**
     * Returns the <code>ServerSocketFactory</code> that this endpoint
     * uses to create <code>ServerSocket</code> objects.
     *
     * @return the socket factory that this endpoint uses to create
     * sockets, or <code>null</code> if no factory is used
     **/
    public ServerSocketFactory getServerSocketFactory() {
	return ssf;
    }

    /**
     * {@inheritDoc}
     *
     * @throws NullPointerException {@inheritDoc}
     **/
    public InvocationConstraints checkConstraints(
	InvocationConstraints constraints)
	throws UnsupportedConstraintException
    {
	return Constraints.check(constraints, true);
    }

    /**
     * Passes the {@link net.jini.jeri.ServerEndpoint.ListenEndpoint
     * ListenEndpoint} for this <code>TcpServerEndpoint</code> to
     * <code>listenContext</code>, which will ensure an active listen
     * operation on the endpoint, and returns a
     * <code>TcpEndpoint</code> instance corresponding to the listen
     * operation chosen by <code>listenContext</code>.
     *
     * <p>If this <code>TcpServerEndpoint</code>'s host name is not
     * <code>null</code>, then the returned <code>TcpEndpoint</code>
     * will contain that host name.  If this
     * <code>TcpServerEndpoint</code>'s host name is
     * <code>null</code>, then this method invokes {@link
     * InetAddress#getLocalHost InetAddress.getLocalHost} to obtain an
     * <code>InetAddress</code> for the local host.  If
     * <code>InetAddress.getLocalHost</code> throws an exception, this
     * method throws that exception.  The returned
     * <code>TcpEndpoint</code>'s host name will be the string
     * returned by invoking {@link InetAddress#getHostAddress
     * getHostAddress} on that <code>InetAddress</code>.  If there is
     * a security manager, its {@link
     * SecurityManager#checkConnect(String,int) checkConnect} method
     * will be invoked with the string returned by invoking {@link
     * InetAddress#getHostName getHostName} on that same
     * <code>InetAddress</code> as the host argument and
     * <code>-1</code> as the port argument; this could result in a
     * <code>SecurityException</code>.
     *
     * <p>This method invokes <code>addListenEndpoint</code> on
     * <code>listenContext</code> once, passing a
     * <code>ListenEndpoint</code> as described below.  If
     * <code>addListenEndpoint</code> throws an exception, then this
     * method throws that exception.  Otherwise, this method returns a
     * <code>TcpEndpoint</code> instance with the host name described
     * above, the TCP port number bound by the listen operation
     * represented by the {@link
     * net.jini.jeri.ServerEndpoint.ListenHandle ListenHandle}
     * returned by <code>addListenEndpoint</code>, and the same
     * <code>SocketFactory</code> as this
     * <code>TcpServerEndpoint</code>.
     *
     * <p>The <code>ListenEndpoint</code> passed to
     * <code>addListenEndpoint</code> represents the TCP port number
     * and <code>ServerSocketFactory</code> of this
     * <code>TcpServerEndpoint</code>.  Its methods behave as follows:
     *
     * <p>{@link net.jini.jeri.ServerEndpoint.ListenEndpoint#listen
     * ListenHandle listen(RequestDispatcher)}:
     *
     * <blockquote>
     *
     * Listens for requests received on this endpoint's TCP port,
     * dispatching them to the supplied <code>RequestDispatcher</code>
     * in the form of {@link InboundRequest} instances.
     *
     * <p>When the implementation of this method needs to create a new
     * <code>ServerSocket</code>, it will do so by invoking one of the
     * <code>createServerSocket</code> methods that returns a bound
     * server socket on the contained <code>ServerSocketFactory</code>
     * if non-<code>null</code>, or it will create a
     * <code>ServerSocket</code> directly otherwise.
     *
     * <p>If there is a security manager, its {@link
     * SecurityManager#checkListen checkListen} method will be invoked
     * with this endpoint's TCP port; this could result in a
     * <code>SecurityException</code>.  Furthermore, before a given
     * <code>InboundRequest</code> gets dispatched to the supplied
     * request dispatcher, the security manager's {@link
     * SecurityManager#checkAccept checkAccept} method must have been
     * successfully invoked in the security context of this
     * <code>listen</code> invocation with the remote IP address and
     * port of the <code>Socket</code> used to receive the request.
     * The <code>checkPermissions</code> method of the dispatched
     * <code>InboundRequest</code> also performs this latter security
     * check.  (Note that in some cases, the implementation may carry
     * out these security checks indirectly, such as through
     * invocations of <code>ServerSocket</code>'s constructors or
     * <code>accept</code> method.)
     *
     * <p>Requests will be dispatched in a {@link PrivilegedAction}
     * wrapped by a {@link SecurityContext} obtained when this method
     * was invoked, with the {@link AccessControlContext} of that
     * <code>SecurityContext</code> in effect.
     *
     * <p>Dispatched requests will implement {@link
     * InboundRequest#populateContext populateContext} to populate the
     * given collection with an element that implements the {@link
     * ClientHost} interface.  That <code>ClientHost</code> element
     * implements {@link ClientHost#getClientHost getClientHost} to
     * return the IP address of the <code>Socket</code> that the
     * request was received over (see {@link Socket#getInetAddress}).
     *
     * <p>Throws {@link IOException} if an I/O exception occurs while
     * performing this operation, such as if the TCP port is already
     * in use.
     *
     * <p>Throws {@link SecurityException} if there is a security
     * manager and the invocation of its <code>checkListen</code>
     * method fails.
     *
     * <p>Throws {@link NullPointerException} if
     * <code>requestDispatcher</code> is <code>null</code>
     *
     * </blockquote>
     *
     * <p>{@link
     * net.jini.jeri.ServerEndpoint.ListenEndpoint#checkPermissions
     * void checkPermissions()}:
     *
     * <blockquote>
     *
     * Verifies that the current security context has all of the
     * security permissions necessary to listen for requests on this
     * endpoint.
     *
     * <p>If there is a security manager, its <code>checkListen</code>
     * method will be invoked with this endpoint's TCP port; this
     * could result in a <code>SecurityException</code>.
     *
     * <p>Throws {@link SecurityException} if there is a security
     * manager and the invocation of its <code>checkListen</code>
     * method fails.
     *
     * </blockquote>
     *
     * <p>{@link Object#equals boolean equals(Object)}:
     *
     * <blockquote>
     *
     * Compares the specified object with this
     * <code>ListenEndpoint</code> for equality.
     *
     * <p>This method returns <code>true</code> if and only if
     *
     * <ul>
     *
     * <li>the specified object is also a <code>ListenEndpoint</code>
     * produced by a <code>TcpServerEndpoint</code>,
     *
     * <li>the port in the specified object is equal to the port in
     * this object, and
     *
     * <li>either this object and the specified object both have no
     * <code>ServerSocketFactory</code> or the
     * <code>ServerSocketFactory</code> in the specified object has
     * the same class and is equal to the one in this object.
     *
     * </ul>
     *
     * </blockquote>
     *
     * @param listenContext the <code>ListenContext</code> to pass
     * this <code>TcpServerEndpoint</code>'s
     * <code>ListenEndpoint</code> to
     *
     * @return the <code>TcpEndpoint</code> instance for sending
     * requests to this <code>TcpServerEndpoint</code>'s endpoint
     * being listened on
     *
     * @throws UnknownHostException if this
     * <code>TcpServerEndpoint</code>'s host name is <code>null</code>
     * and <code>InetAddress.getLocalHost</code> throws an
     * <code>UnknownHostException</code>
     *
     * @throws IOException if an I/O exception occurs while performing
     * this operation, such as if the TCP port is already in use
     *
     * @throws SecurityException if there is a security manager and
     * either the invocation of its <code>checkListen</code> method
     * fails or this <code>TcpServerEndpoint</code>'s host name is
     * <code>null</code> and the invocation of the security manager's
     * <code>checkConnect</code> method fails
     *
     * @throws IllegalArgumentException {@inheritDoc}
     *
     * @throws NullPointerException {@inheritDoc}
     **/
    public Endpoint enumerateListenEndpoints(ListenContext listenContext)
	throws IOException
    {
	if (listenContext == null) {
	    throw new NullPointerException();
	}

	String localHost = host;
	if (localHost == null) {
	    InetAddress localAddr;
	    try {
		localAddr = (InetAddress) AccessController.doPrivileged(
		    new PrivilegedExceptionAction() {
			public Object run() throws UnknownHostException {
			    return InetAddress.getLocalHost();
			}
		    });
	    } catch (PrivilegedActionException e) {
		throw (UnknownHostException) e.getCause();
	    }
	    SecurityManager sm = System.getSecurityManager();
	    if (sm != null) {
		try {
		    sm.checkConnect(localAddr.getHostName(), -1);
		} catch (SecurityException e) {
		    throw new SecurityException(
			"access to resolve local host denied");
		}
	    }
	    localHost = localAddr.getHostAddress();
	}

	LE listenEndpoint = new LE(); // REMIND: needn't be new?
	ListenCookie listenCookie =
	    listenContext.addListenEndpoint(listenEndpoint);

	if (!(listenCookie instanceof LE.Cookie)) {
	    throw new IllegalArgumentException();
	}
	LE.Cookie cookie = (LE.Cookie) listenCookie;
	if (!listenEndpoint.equals(cookie.getLE())) {
	    throw new IllegalArgumentException();
	}

	return TcpEndpoint.getInstance(localHost, cookie.getPort(), sf);
    }

    /**
     * Returns the hash code value for this
     * <code>TcpServerEndpoint</code>.
     *
     * @return the hash code value for this
     * <code>TcpServerEndpoint</code>
     **/
    public int hashCode() {
	return port ^
	    (host != null ? host.hashCode() : 0) ^
	    (sf != null ? sf.hashCode() : 0) ^
	    (ssf != null ? ssf.hashCode() : 0);
    }

    /**
     * Compares the specified object with this
     * <code>TcpServerEndpoint</code> for equality.
     *
     * <p>This method returns <code>true</code> if and only if
     *
     * <ul>
     *
     * <li>the specified object is also a
     * <code>TcpServerEndpoint</code>,
     *
     * <li>the host and port in the specified object are equal to the
     * host and port in this object,
     *
     * <li>either this object and the specified object both have no
     * <code>SocketFactory</code> or the <code>ServerFactory</code> in
     * the specified object has the same class and is equal to the one
     * in this object, and
     *
     * <li>either this object and the specified object both have no
     * <code>ServerSocketFactory</code> or the
     * <code>ServerSocketFactory</code> in the specified object has
     * the same class and is equal to the one in this object.
     *
     * </ul>
     *
     * @param obj the object to compare with
     *
     * @return <code>true</code> if <code>obj</code> is equivalent to
     * this object; <code>false</code> otherwise
     **/ 
    public boolean equals(Object obj) {
	if (obj == this) {
	    return true;
	} else if (!(obj instanceof TcpServerEndpoint)) {
	    return false;
	}
	TcpServerEndpoint other = (TcpServerEndpoint) obj;
	return
	    Util.equals(host, other.host) &&
	    port == other.port &&
	    Util.sameClassAndEquals(sf, other.sf) &&
	    Util.sameClassAndEquals(ssf, other.ssf);
    }

    /**
     * Returns a string representation of this
     * <code>TcpServerEndpoint</code>.
     *
     * @return a string representation of this
     * <code>TcpServerEndpoint</code>
     **/
    public String toString() {
	return "TcpServerEndpoint[" +
	    (host != null ? host + ":" : "") + port +
	    (ssf != null ? "," + ssf : "") +
	    (sf != null ? "," + sf : "") + "]";
    }

    /**
     * ListenEndpoint implementation.
     **/
    private class LE implements ListenEndpoint {

	LE() { }

	public void checkPermissions() {
	    SecurityManager sm = System.getSecurityManager();
	    if (sm != null) {
		sm.checkListen(port);
	    }
	}

	public ListenHandle listen(RequestDispatcher requestDispatcher)
	    throws IOException
	{
	    if (requestDispatcher == null) {
		throw new NullPointerException();
	    }

	    ServerSocket serverSocket;
	    if (ssf != null) {
		serverSocket = ssf.createServerSocket(port);
	    } else {
		if (useNIO) {
		    serverSocket = ServerSocketChannel.open().socket();
		    serverSocket.bind(new InetSocketAddress(port));
		} else {
		    serverSocket = new ServerSocket(port);
		}
	    }

	    if (logger.isLoggable(Level.FINE)) {
		logger.log(Level.FINE,
			   "created server socket {0}" +
			   (ssf != null ? " using factory {1}" : ""),
			   new Object[]{ serverSocket, ssf });
	    }

	    Cookie cookie = new Cookie(serverSocket.getLocalPort());
	    final LH listenHandle = new LH(requestDispatcher, serverSocket,
					   Security.getContext(), cookie);
	    listenHandle.startAccepting();
	    return listenHandle;
	}

	// following are required to implement equals:
	private int getPort() { return port; }
	private ServerSocketFactory getSSF() { return ssf; }

	public int hashCode() {
	    return port ^ (ssf != null ? ssf.hashCode() : 0);
	}

	public boolean equals(Object obj) {
	    if (obj == this) {
		return true;
	    } else if (!(obj instanceof LE)) {
		return false;
	    }
	    LE other = (LE) obj;
	    return port == other.getPort() &&
		Util.sameClassAndEquals(ssf, other.getSSF());
	}

	public String toString() {
	    return "TcpServerEndpoint.LE[" + port +
		(ssf != null ? "," + ssf : "") + "]";
	}

	/**
	 * ListenCookie implementation: identifies a listen operation
	 * by containing the local port that the server socket is
	 * bound to.
	 **/
	private class Cookie implements ListenCookie {

	    private final int port;

	    Cookie(int port) { this.port = port; }

	    LE getLE() { return LE.this; }
	    int getPort() { return port; }

	    public String toString() {
		return "TcpServerEndpoint.LE.Cookie[" + port + "]";
	    }
	}
    }

    /**
     * ListenHandle implementation: represents a listen operation.
     **/
    private static class LH implements ListenHandle {

	private final RequestDispatcher requestDispatcher;
	private final ServerSocket serverSocket;
	private final SecurityContext securityContext;
	private final ListenCookie cookie;

	private long acceptFailureTime = 0L;	// local to accept thread
	private int acceptFailureCount;		// local to accept thread

	private final Object lock = new Object();
	private boolean closed = false;
	private final Set connections = new HashSet();

	LH(RequestDispatcher requestDispatcher,
	   ServerSocket serverSocket,
	   SecurityContext securityContext,
	   ListenCookie cookie)
	{
	    this.requestDispatcher = requestDispatcher;
	    this.serverSocket = serverSocket;
	    this.securityContext = securityContext;
	    this.cookie = cookie;
	}

	/**
	 * Starts the accept loop.
	 **/
	void startAccepting() {
	    systemThreadPool.execute(
		new Runnable() {
		    public void run() { executeAcceptLoop(); }
		}, toString() + " accept loop");
	}

	/**
	 * Executes the accept loop.
	 *
	 * The accept loop runs with the full privileges of this code;
	 * "accept" SocketPermissions are checked against the direct
	 * user of this endpoint later, when the
	 * ServerConnectionManager calls checkPermissions on the
	 * InboundRequest before passing it to the RequestDispatcher.
	 **/
	private void executeAcceptLoop() {
	    while (true) {
		Socket socket = null;
		try {
		    socket = serverSocket.accept();
		    if (logger.isLoggable(Level.FINE)) {
			logger.log(Level.FINE,
			    "accepted socket {0} from server socket {1}",
			    new Object[]{ socket, serverSocket });
		    }

		    /*
		     * Set socket options.
		     */
		    try {
		    	socket.setTcpNoDelay(true);
		    } catch (SocketException e) {
			// ignore possible failures and proceed anyway
			if (logger.isLoggable(Levels.HANDLED)) {
			    LogRecord lr = new LogRecord(Levels.HANDLED,
				"exception setting TCP_NODELAY on socket {0}");
			    lr.setLoggerName(logger.getName());
			    lr.setSourceClassName(this.getClass().getName());
			    lr.setSourceMethodName("executeAcceptLoop");
			    lr.setParameters(new Object[]{ socket });
			    lr.setThrown(e);
			    logger.log(lr);
			}
		    }
		    try {
			socket.setKeepAlive(true);
		    } catch (SocketException e) {
			// ignore possible failures and proceed anyway
			if (logger.isLoggable(Levels.HANDLED)) {
			    LogRecord lr = new LogRecord(Levels.HANDLED,
				"exception setting SO_KEEPALIVE " +
				"on socket {0}");
			    lr.setLoggerName(logger.getName());
			    lr.setSourceClassName(this.getClass().getName());
			    lr.setSourceMethodName("executeAcceptLoop");
			    lr.setParameters(new Object[]{ socket });
			    lr.setThrown(e);
			    logger.log(lr);
			}
		    }

		    final ServerConnection serverConnection =
			new ServerConnectionImpl(socket);

		    AccessController.doPrivileged(securityContext.wrap(
			new PrivilegedAction() {
			    public Object run() {
				serverConnectionManager.handleConnection(
				    serverConnection, requestDispatcher);
				return null;
			    }
			}), securityContext.getAccessControlContext());

		} catch (Throwable t) {

		    boolean closedSync;
		    synchronized (this) {
			closedSync = closed;
		    }

		    if (!closedSync && logger.isLoggable(Level.INFO)) {
			LogRecord lr = new LogRecord(
			    Level.INFO, "accept loop for {0} throws");
			lr.setLoggerName(logger.getName());
			lr.setSourceClassName(this.getClass().getName());
			lr.setSourceMethodName("executeAcceptLoop");
			lr.setParameters(new Object[]{ serverSocket });
			lr.setThrown(t);
			logger.log(lr);
		    }

		    if (socket != null) {
			try {
			    socket.close();
			} catch (IOException e) {
			}
		    }

		    if (closedSync) {
			break;
		    }
		    
		    if (!(t instanceof SecurityException)) {
			try {
			    // NYI: shed idle connections
			} catch (OutOfMemoryError e) {
			} catch (Exception e) {
			}
		    }

		    if ((t instanceof IOException) ||
			(t instanceof SecurityException) ||
			(t instanceof RuntimeException) ||
			(t instanceof OutOfMemoryError) ||
			(t instanceof NoClassDefFoundError))
		    {
			if (continueAfterAcceptFailure(t) == false) {
			    return;
			}
		    } else {
			/*
			 * REMIND: Do we really want to give up here,
			 * for other Errors?
			 */
			throw (Error) t;
		    }
		}
	    }
	}

	/**
	 * Stops this listen operation.
	 **/
	public void close() {
	    synchronized (lock) {
		if (closed) {
		    return;
		}
		closed = true;
	    }

	    try {
		serverSocket.close();
	    } catch (IOException e) {
	    }
	    if (logger.isLoggable(Level.FINE)) {
		logger.log(Level.FINE, "closed server socket {0}", 
		    new Object[]{ serverSocket });
	    }

	    /*
	     * Iterating over connections without synchronization is
	     * safe at this point because no other thread will access
	     * it without verifying that closed is false in a
	     * synchronized block first.
	     */
	    for (Iterator i = connections.iterator(); i.hasNext();) {
		((ServerConnectionImpl) i.next()).close();
	    }
	}

	/**
	 * Returns a cookie to identify this listen operation.
	 **/
	public ListenCookie getCookie() {
	    return cookie;
	}

	public String toString() {
	    return "TcpServerEndpoint.LH[" + serverSocket + "]";
	}

	/**
	 * Throttles the accept loop after ServerSocket.accept throws
	 * an exception, and decides whether to continue at all.  The
	 * current code is borrowed from the JRMP implementation; it
	 * always continues, but it delays the loop after bursts of
	 * failed accepts.
	 **/
	private boolean continueAfterAcceptFailure(Throwable t) {
	    /*
	     * If we get a burst of NFAIL failures in NMSEC milliseconds,
	     * then wait for ten seconds.  This is to ensure that individual
	     * failures don't cause hiccups, but sustained failures don't
	     * hog the CPU in futile accept-fail-retry looping.
	     */
	    final int NFAIL = 10;
	    final int NMSEC = 5000;
	    long now = System.currentTimeMillis();
	    if (acceptFailureTime == 0L ||
		(now - acceptFailureTime) > NMSEC)
	    {
		// failure time is very old, or this is first failure
		acceptFailureTime = now;
		acceptFailureCount = 0;
	    } else {
		// failure window was started recently
		acceptFailureCount++;
		if (acceptFailureCount >= NFAIL) {
		    try {
			Thread.sleep(10000);
		    } catch (InterruptedException ignore) {
		    }
		    // no need to reset counter/timer
		}
	    }
	    return true;
	}

	/**
	 * ServerConnection implementation.
	 *
	 * Instances of this class should never get exposed to
	 * anything other than our ServerConnectionManager, which we
	 * trust to operate correctly, so we do not bother to validate
	 * request handles passed in.
	 **/
	private class ServerConnectionImpl implements ServerConnection {

	    private final Socket socket;

	    ServerConnectionImpl(Socket socket) {
		this.socket = socket;
		addToConnectionSet();
	    }

	    public InputStream getInputStream() throws IOException {
		return socket.getInputStream();
	    }

	    public OutputStream getOutputStream() throws IOException {
		return socket.getOutputStream();
	    }

	    public SocketChannel getChannel() {
		return socket.getChannel();
	    }

	    public InboundRequestHandle processRequestData(InputStream in,
							   OutputStream out)
	    {
		return new InboundRequestHandle() { };
	    }

	    public void checkPermissions(InboundRequestHandle handle) {
		SecurityManager sm = System.getSecurityManager();
		if (sm != null) {
		    sm.checkAccept(socket.getInetAddress().getHostAddress(),
				   socket.getPort());
		}
	    }

	    public InvocationConstraints checkConstraints(
		InboundRequestHandle handle,
		InvocationConstraints constraints)
		throws UnsupportedConstraintException
	    {
		/*
		 * The transport layer aspects of all constraints
		 * supported by this transport provider are always
		 * satisfied by all requests on the server side, so
		 * this method can have the same static behavior as
		 * ServerCapabilities.checkConstraints does.
		 * (Otherwise, this operation would need to be
		 * parameterized by this connection or the request.)
		 */
		return Constraints.check(constraints, true);
	    }

    	    public void populateContext(InboundRequestHandle handle,
					Collection context)
	    {
		Util.populateContext(context, socket.getInetAddress());
	    }

	    public void close() {
		try {
		    socket.close();	// this will bring mux down too
		} catch (IOException e) {
		}
		if (logger.isLoggable(Level.FINE)) {
		    logger.log(Level.FINE, "closed socket {0}", 
			       new Object[]{ socket });
		}

		synchronized (lock) {
		    if (!closed) {	// must not mutate set after closed
			connections.remove(this);
		    }
		}
	    }

	    private void addToConnectionSet() {
		boolean needClose = false;
		synchronized (lock) {
		    if (closed) {
			needClose = true; // close after releasing lock
		    } else {
			connections.add(this);
		    }
		}
		if (needClose) {
		    close();
		}
	    }
	}
    }
}
