/*
 * 
 * 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.ssl;

import com.sun.jini.action.GetLongAction;
import com.sun.jini.jeri.internal.http.HttpClientConnection;
import com.sun.jini.jeri.internal.http.HttpClientManager;
import com.sun.jini.jeri.internal.http.HttpClientSocketFactory;
import com.sun.jini.jeri.internal.http.HttpSettings;
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.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamField;
import java.io.OutputStream;
import java.io.Serializable;
import java.net.Socket;
import java.security.AccessController;
import java.security.Principal;
import java.security.PrivilegedAction;
import java.security.SecureRandom;
import java.security.cert.CertPath;
import java.security.cert.CertificateFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.SocketFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import javax.security.auth.Subject;
import javax.security.auth.x500.X500Principal;
import javax.security.auth.x500.X500PrivateCredential;
import net.jini.core.constraint.ClientAuthentication;
import net.jini.core.constraint.ClientMaxPrincipal;
import net.jini.core.constraint.ClientMaxPrincipalType;
import net.jini.core.constraint.ClientMinPrincipal;
import net.jini.core.constraint.ClientMinPrincipalType;
import net.jini.core.constraint.Confidentiality;
import net.jini.core.constraint.ConnectionAbsoluteTime;
import net.jini.core.constraint.ConnectionRelativeTime;
import net.jini.core.constraint.ConstraintAlternatives;
import net.jini.core.constraint.Delegation;
import net.jini.core.constraint.DelegationAbsoluteTime;
import net.jini.core.constraint.DelegationRelativeTime;
import net.jini.core.constraint.Integrity;
import net.jini.core.constraint.InvocationConstraints;
import net.jini.core.constraint.ServerAuthentication;
import net.jini.core.constraint.ServerMinPrincipal;
import net.jini.io.context.AcknowledgmentSource;
import net.jini.jeri.BasicJeriExporter;
import net.jini.jeri.Endpoint;
import net.jini.jeri.OutboundRequest;
import net.jini.jeri.OutboundRequest;
import net.jini.jeri.OutboundRequestIterator;
import net.jini.jeri.ServerEndpoint;
import net.jini.jeri.connection.Connection;
import net.jini.jeri.connection.ConnectionManager;
import net.jini.jeri.connection.OutboundRequestHandle;
import net.jini.security.AuthenticationPermission;
import net.jini.security.Security;
import net.jini.security.proxytrust.TrustEquivalence;

/**
 * An implementation of {@link Endpoint} that uses HTTPS (HTTP over TLS/SSL) to
 * support invocation constraints for communication through firewalls. <p>
 *
 * Instances of this class are intended to be created by the {@link
 * BasicJeriExporter} class when it calls {@link
 * ServerEndpoint#enumerateListenEndpoints enumerateListenEndpoints} on
 * instances of {@link HttpsServerEndpoint}. <p>
 *
 * The {@link SslTrustVerifier} trust verifier may be used for establishing
 * trust in remote proxies that use instances of this class. <p>
 *
 * This class supports at least the following constraints, possibly limited by
 * the available cipher suites: <p>
 *
 * <ul>
 * <li> {@link ClientAuthentication}
 * <li> {@link ClientMaxPrincipal}, when it contains an {@link X500Principal}
 * <li> {@link ClientMaxPrincipalType}, when it contains
 *	<code>X500Principal</code>
 * <li> {@link ClientMinPrincipal}, when it contains a single
 *	<code>X500Principal</code> only
 * <li> {@link ClientMinPrincipalType}, when it contains
 *	<code>X500Principal</code> only
 * <li> {@link Confidentiality}
 * <li> {@link ConfidentialityStrength}, a provider-specific constraint for
 *	specifying weak or strong confidentiality
 * <li> {@link ConnectionAbsoluteTime}
 * <li> {@link ConstraintAlternatives}, if the elements all have the same
 *	actual class and at least one element is supported
 * <li> {@link Delegation#NO}
 * <li> {@link Delegation#YES}, trivially, for anonymous clients
 * <li> {@link DelegationAbsoluteTime}, trivially, when delegation is not
 *	supported
 * <li> {@link Integrity#YES}
 * <li> {@link ServerAuthentication}
 * <li> {@link ServerMinPrincipal}, when it contains a single
 *	<code>X500Principal</code> only
 * </ul> <p>
 *
 * Note that {@link ConnectionRelativeTime} and {@link DelegationRelativeTime}
 * constraints may be used at higher levels, but should be converted to the
 * associated absolute time constraints for use by this class. <p>
 *
 * This class authenticates as a single {@link Principal} if the following
 * items are present in the current {@link Subject}: <p>
 *
 * <ul>
 * <li> One or more principals of type <code>X500Principal</code>
 * <li> For each principal, one or more certificate chains, stored as public
 *      credentials, and represented by instances of {@link CertPath}, whose
 *      <code>getType</code> method returns "X.509", and for which calling
 *      <code>getSubjectDN</code> on the certificate chain's first element
 *      returns that principal's name
 * <li> For each certificate chain, an instance of {@link
 *      X500PrivateCredential}, stored as a private credential, whose
 *      <code>getCertificate</code> method returns a value equal to the first
 *      element of the certificate chain, and whose <code>getPrivateKey</code>
 *      method returns the associated private key
 * </ul> <p>
 *
 * In addition, this class's {@link #newRequest newRequest} method will only
 * authenticate as a given principal if the caller has been granted {@link
 * AuthenticationPermission} with that principal as the local principal, the
 * principal representing the authenticated identity of the server as the peer
 * principal, and the <code>connect</code> action. <p>
 *
 * This class supports remote connections between authenticated servers and
 * authenticated or anonymous clients, and between anonymous servers and
 * anonymous clients. Connections between anonymous servers and authenticated
 * clients are not supported. Because of the suites available in the TLS/SSL
 * protocol, support for {@link Confidentiality#NO} requires the server to
 * authenticate with an RSA public key. <p>
 *
 * This class permits specifying a {@link SocketFactory} for creating the
 * {@link Socket} instances that it uses to make remote connections. These
 * socket factories should not be instances of {@link SSLSocketFactory} or
 * return instances {@link SSLSocket}; it is the responsibility of the
 * implementation to establish a TLS/SSL connection over the socket it obtains
 * from the socket factory. <p>
 *
 * A <code>SocketFactory</code> used with instances of this class should be
 * serializable, and must implement {@link Object#equals Object.equals} to
 * obey the guidelines that are specified for <code>equals</code> methods of
 * {@link Endpoint} instances. <p>
 *
 * This class recognizes the following system properties: <p>
 * 
 * <ul>
 * <li> https.proxyHost - The host name for the secure proxy server. The
 *	default is to use no proxy server.
 * <li> https.proxyPort - The port for the secure proxy server. The default is
 *	443.
 * <li> http.nonProxyHosts - The names of hosts for which direct connections
 *	should be made rather than using the proxy server. Each host name may
 *	contain '<code>*</code>' wildcard characters in any position to match
 *	zero or more of any characters within the name. Multiple host names may
 *	be specified by separating the names with '<code>|</code>'
 *	characters. The default is for all connections to use the proxy server
 *	if one is specified.
 * </ul>
 *
 * @author Sun Microsystems, Inc.
 * @see HttpsServerEndpoint
 * @see ConfidentialityStrength
 * @see SslTrustVerifier
 * @since 2.0
 *
 * @com.sun.jini.impl <!-- Implementation Specifics -->
 *
 * This implementation uses the <a
 * href="http://java.sun.com/j2se/1.4/docs/guide/security/jsse/JSSERefGuide.html"
 * target="_top">Java(TM) Secure Socket Extension (JSSE)</a>. <p>
 *
 * This implementation uses the {@link ConnectionManager} class to manage
 * connections. <p>
 *
 * This implementation uses the following {@link Logger} instances in the
 * <code>net.jini.jeri.ssl</code> namespace: <p>
 *
 * <ul>
 * <li> <a href="#init_logger">init</a> - problems during initialization
 * <li> <a href="#client_logger">client</a> - information about
 *	client-side connections
 * </ul> <p>
 *
 * <a name="init_logger"></a>
 * <table border="1" cellpadding="5" summary="Describes logging to the init
 *	  logger performed by the HttpsEndpoint class at different logging
 *	  levels">
 *
 * <caption halign="center" valign="top"><b><code>
 *	    net.jini.jeri.ssl.init</code></b></caption>
 *     
 * <tr> <th scope="col"> Level <th scope="col"> Description
 *
 * <tr> <td> {@link Level#WARNING WARNING} <td> problems with initializing JSSE
 *
 * </table> <p>
 *
 * <a name="client_logger"></a>
 * <table border="1" cellpadding="5" summary="Describes logging to the client
 *	  logger performed by the HttpsEndpoint class at different logging
 *	  levels">
 *
 * <caption halign="center" valign="top"><b><code>
 *	    net.jini.jeri.ssl.client</code></b></caption>
 *     
 * <tr> <th scope="col"> Level <th scope="col"> Description
 *
 * <tr> <td> {@link Levels#FAILED FAILED} <td> problems with outbound requests
 *
 * <tr> <td> {@link Levels#HANDLED HANDLED} <td> exceptions caught involving
 * authentication
 *
 * <tr> <td> {@link Level#FINE FINE} <td> authentication decisions; creating,
 * choosing, expiring, or closing connections; or handling outbound requests
 *
 * <tr> <td> {@link Level#FINEST FINEST} <td> low level operation tracing
 *
 * </table> <p>
 *
 * This implementation uses the following security providers: <p>
 *
 * <ul>
 * <li> {@link SSLContext}, with the protocol specified by the
 *	<code>com.sun.jini.jeri.ssl.sslProtocol</code> system property, or
 *	<code>"TLS"</code> if that property is not defined, to provide the
 *	TLS/SSL implementation. The {@link SSLContext#init SSLContext.init}
 *	method is called with <code>null</code> for the <code>random</code>
 *	parameter to use the default {@link SecureRandom} implementation.
 * <li> {@link CertificateFactory}, with type <code>"X.509"</code>, to generate
 *	<code>CertPath</code> instances from X.509 certificate chains
 * <li> {@link TrustManagerFactory}, with the algorithm specified by the
 *	<code>com.sun.jini.jeri.ssl.trustManagerFactoryAlgorithm</code> system
 *	property, or the default algorithm if that property is not defined, to
 *	implement trust management for the TLS/SSL implementation. The factory
 *	must return trust managers that implement {@link X509TrustManager}.
 * </ul> <p>
 * 
 * See the documentation on <a
 * href="http://java.sun.com/j2se/1.4/docs/guide/security/CryptoSpec.html#ProviderInstalling"
 * target="_top">installing security providers</a> and <a
 * href="http://java.sun.com/j2se/1.4/docs/guide/security/jsse/JSSERefGuide.html#ProviderCust"
 * target="_top">configuring JSSE</a> for information on configuring these
 * providers. <p>
 *
 * The <a
 * href="http://java.sun.com/j2se/1.4/docs/guide/security/jsse/JSSERefGuide.html#Customization"
 * target="_top">JSSE documentation</a> also describes the system properties
 * for configuring the location, type, and password of the truststore that this
 * implementation uses, through JSSE, to make decisions about what certificate
 * chains should be trusted. <p>
 * 
 * This implementation recognizes the following system properties: <p>
 * 
 * <ul>
 * <li> <code>com.sun.jini.jeri.ssl.maxClientSessionDuration</code> - The
 *	maximum number of milliseconds a client-side TLS/SSL session should be
 *	used. The default is 23.5 hours. The value should be smaller than the
 *	maximum server session duration to allow the client to negotiate a new
 *	session before the server timeout occurs.
 * <li> <code>com.sun.jini.jeri.ssl.sslProtocol</code> - The secure socket
 *	protocol used when obtaining {@link SSLContext} instances. The default
 *	is <code>"TLS"</code>.
 * <li> <code>com.sun.jini.jeri.ssl.trustManagerFactoryAlgorithm</code> - The
 *	algorithm used when obtaining {@link TrustManagerFactory}
 *	instances. The default is the value returned by {@link
 *	TrustManagerFactory#getDefaultAlgorithm
 *	TrustManagerFactory.getDefaultAlgorithm}.
 * <li> <code>com.sun.jini.jeri.ssl.cipherSuites</code> - The TLS/SSL cipher
 *	suites that should be used for communication. The default is the list
 *	of suites supported by the JSSE implementation. The value should
 *	specify the suite names, separated by commas. The value will be ignored
 *	if it contains no suites or specifies suites that are not supported by
 *	the JSSE implementation. Suites appearing earlier in the list will be
 *	preferred to ones appearing later for suites that support the same
 *	requirements and preferences.
 * <li> <code>com.sun.jini.jeri.https.idleConnectionTimeout</code> - The number
 *	of milliseconds to retain idle client-side HTTPS connections before
 *	closing them. The default is <code>15000</code>.
 * <li> <code>com.sun.jini.jeri.https.responseAckTimeout</code> - The number of
 *	milliseconds to keep track of acknowledgments that have not yet been
 *	sent for {@link AcknowledgmentSource} instances. The default is
 *	<code>15000</code>.
 * </ul>
 */
public final class HttpsEndpoint
    implements Endpoint, Serializable, TrustEquivalence
{
    /* -- Fields -- */

    private static final long serialVersionUID = -3438786823613900804L;

    /**
     * @serialField serverHost String
     *		    The name of the server host.
     * @serialField port int
     *		    The server port.
     * @serialField socketFactory SocketFactory
     *		    The socket factory for creating sockets, or
     *		    <code>null</code> to use default sockets.
     */
    private static final ObjectStreamField[] serialPersistentFields = {
	new ObjectStreamField("serverHost", String.class),
	new ObjectStreamField("port", int.class),
	new ObjectStreamField("socketFactory", SocketFactory.class)
    };

    /**
     * Maps HttpsEndpointImpls to EndpointInfo instances, each of which
     * contains a representative endpoint equal to the key and a lists of idle
     * connections.
     */
    static final Map endpointMap = new HashMap();

    /** How long to leave idle connections around before closing them. */
    static final long IDLE_TIMEOUT =
	((Long) Security.doPrivileged(
	    new GetLongAction(
		"com.sun.jini.jeri.https.idleConnectionTimeout",
		15000))).longValue();

    /** Executes a Runnable in a system thread. */
    static final Executor systemExecutor = (Executor)
	Security.doPrivileged(new GetThreadPoolAction(false));

    /** How long to wait for acknowledgments */
    private static final long ACK_TIMEOUT =
	((Long) Security.doPrivileged(
	    new GetLongAction(
		"com.sun.jini.jeri.https.responseAckTimeout",
		15000))).longValue();

    /** Manager for HTTP client connections. */
    static final HttpClientManager httpClientManager =
	new HttpClientManager(ACK_TIMEOUT);

    /** Implementation delegate. */
    private transient HttpsEndpointImpl impl;

    /* -- Methods -- */

    /**
     * Returns an HTTPS endpoint for the specified server host and port. Uses a
     * <code>null</code> socket factory to create default sockets.
     *
     * @param serverHost the name of the server host
     * @param port the server port
     * @return an <code>HttpsEndpoint</code> instance
     * @throws IllegalArgumentException if <code>port</code> is less than or
     *	       equal to <code>0</code>, or greater than <code>65535</code>
     * @throws NullPointerException if <code>serverHost</code> is
     *	       <code>null</code>
     */
    public static HttpsEndpoint getInstance(String serverHost, int port) {
	return new HttpsEndpoint(serverHost, port, null);
    }

    /**
     * Returns an HTTPS endpoint for the specified server host, port, and
     * socket factory. A <code>socketFactory</code> of <code>null</code> uses
     * default sockets.
     *
     * @param serverHost the name of the server host
     * @param port the server port
     * @param socketFactory the socket factory, or <code>null</code>
     * @return an <code>HttpsEndpoint</code> instance
     * @throws IllegalArgumentException if <code>port</code> is less than or
     *	       equal to <code>0</code>, or greater than <code>65535</code>
     * @throws NullPointerException if <code>serverHost</code> is
     *	       <code>null</code>
     */
    public static HttpsEndpoint getInstance(
	String serverHost, int port, SocketFactory socketFactory)
    {
	return new HttpsEndpoint(serverHost, port, socketFactory);
    }

    /** Creates an instance of this class. */
    private HttpsEndpoint(
	String serverHost, int port, SocketFactory socketFactory)
    {
	impl = new HttpsEndpointImpl(this, serverHost, port, socketFactory);
    }

    /**
     * Returns the server host that this endpoint connects to.
     *
     * @return the server host that this endpoint connects to
     */
    public String getHost() {
	return impl.serverHost;
    }

    /**
     * Returns the TCP port that this endpoint connects to.
     *
     * @return the TCP port that this endpoint connects to
     */
    public int getPort() {
	return impl.port;
    }

    /**
     * Returns the socket factory that this endpoint uses to create {@link
     * Socket} instances, or <code>null</code> if it uses default sockets.
     *
     * @return the socket factory that this endpoint uses to create sockets, or
     *	       <code>null</code> if it uses default sockets
     */
    public SocketFactory getSocketFactory() {
	return impl.socketFactory;
    }

    /** Returns a string representation of this object. */
    public String toString() {
	return "HttpsEndpoint" + impl.fieldsToString();
    }

    /* -- Implement Endpoint -- */

    /** Returns a hash code value for this object. */
    public int hashCode() {
	return impl.hashCode();
    }

    /**
     * Two instances of this class are equal if they have the same values for
     * server host and port; and have socket factories that are either both
     * <code>null</code>, or have the same actual class and are equal.
     */
    public boolean equals(Object object) {
	if (this == object) {
	    return true;
	} else {
	    return object instanceof HttpsEndpoint &&
		impl.equals(((HttpsEndpoint) object).impl);
	}
    }

    /**
     * {@inheritDoc} <p>
     *
     * If there is a security manager, the {@link OutboundRequestIterator#next
     * next} method defined on the iterator returned by this method calls the
     * security manager's {@link SecurityManager#checkConnect checkConnect}
     * method with the endpoint's server host and port.
     *
     * @throws NullPointerException {@inheritDoc}
     */
    public OutboundRequestIterator newRequest(
	InvocationConstraints constraints)
    {
	return impl.newRequest(constraints);
    }

    /* -- Implement TrustEquivalence -- */

    /**
     * Returns <code>true</code> if the argument is an instance of
     * <code>HttpsEndpoint</code> with the same values for server host and
     * port; and either both this instance and the argument have
     * <code>null</code> socket factories, or the factories have the same
     * actual class and are equal; and returns <code>false</code> otherwise.
     */
    public boolean checkTrustEquivalence(Object obj) {
	if (this == obj) {
	    return true;
	} else {
	    return obj instanceof HttpsEndpoint &&
		impl.equals(((HttpsEndpoint) obj).impl);
	}
    }

    /* -- Serialization -- */

    /**
     * Writes the <code>serverHost</code>, <code>port</code>, and
     * <code>socketFactory</code> fields.
     */
    private void writeObject(ObjectOutputStream out) throws IOException {
	ObjectOutputStream.PutField fields = out.putFields();
	fields.put("serverHost", impl.serverHost);
	fields.put("port", impl.port);
	fields.put("socketFactory", impl.socketFactory);
	out.writeFields();
    }

    /**
     * Reads the <code>serverHost</code>, <code>port</code>, and
     * <code>socketFactory</code> fields, checks that <code>serverHost</code>
     * is not <code>null</code> and <code>port</code> is in range, and sets
     * transient fields.
     *
     * @throws InvalidObjectException if <code>serverHost</code> is
     * <code>null</code> or <code>port</code> is out of range
     */
    private void readObject(ObjectInputStream in)
	throws IOException, ClassNotFoundException
    {
	ObjectInputStream.GetField fields = in.readFields();
	String serverHost = (String) fields.get("serverHost", null);
	int port = fields.get("port", 0);
	SocketFactory socketFactory =
	    (SocketFactory) fields.get("socketFactory", null);
	if (serverHost == null) {
	    throw new InvalidObjectException("serverHost cannot be null");
	} else if  (port <= 0 || port > 0xFFFF) {
	    throw new InvalidObjectException("Invalid port: " + port);
	}
	impl = new HttpsEndpointImpl(this, serverHost, port, socketFactory);
    }

    /* -- Classes -- */

    /** Implementation delegate */
    private static final class HttpsEndpointImpl extends SslEndpointImpl {

	/* -- Constructors -- */

	/** Creates an instance of this class. */
	HttpsEndpointImpl(Endpoint endpoint,
			  String serverHost,
			  int port,
			  SocketFactory socketFactory)
	{
	    super(endpoint, serverHost, port, socketFactory);
	}

	/* -- Methods -- */

	/**
	 * Implements Endpoint.newRequest when the constraints are supported.
	 */
	OutboundRequestIterator newRequest(final CallContext callContext) {
	    return new OutboundRequestIterator() {
		private boolean done;
		public synchronized boolean hasNext() {
		    return !done;
		}
		public synchronized OutboundRequest next() throws IOException {
		    if (!hasNext()) {
			throw new NoSuchElementException();
		    }
		    done = true;
		    return getOutboundRequest(callContext);
		}
	    };
	}

	/** Returns an outbound request for the specified call context. */
	OutboundRequest getOutboundRequest(CallContext callContext)
	    throws IOException
	{
	    HttpsConnection connection = null;
	    EndpointInfo info;
	    synchronized (endpointMap) {
		info = (EndpointInfo) endpointMap.get(this);
		if (info == null) {
		    info = new EndpointInfo(this);
		}
	    }
	    connection = info.connect(callContext);
	    boolean ok = false;
	    try {
		OutboundRequest request = connection.newRequest(callContext);
		ok = true;
		logger.log(Level.FINE, "using {0}", connection);
		return request;
	    } finally {
		if (!ok) {
		    info.noteClosed(connection);
		}
	    }
	}

	/** Creates a new connection. */
	public Connection connect(OutboundRequestHandle handle)
	    throws IOException
	{
	    HttpsConnection connection = new HttpsConnection(
		this, CallContext.coerce(handle, endpoint), serverHost, port,
		socketFactory);
	    connection.establishCallContext();
	    return connection;
	}
    }

    /* Implements Connection and HttpClientSocketFactory. */
    private static final class HttpsConnection extends SslConnection
	implements HttpClientSocketFactory
    {
	/* -- Fields -- */

	/** The associated endpoint. */
	private final HttpsEndpointImpl endpoint;

	/**
	 * The proxy host, as specified by the value of the http.proxyHost
	 * system property at the time the connection was created, or null if
	 * using a direct connection.
	 */
	private final String proxyHost;

	/** The proxy port, ignored if using a direct connection. */
	private final int proxyPort;

	/** The current HTTP client connection. */
	private HttpClientConnection httpClient;

	/**
	 * The time this connection was found to be idle by the Reaper thread.
	 * Set to zero each time a new request is initiated.
	 */
	private long idleTime = 0;

	/* -- Methods -- */

	/** Creates a connection. */
	HttpsConnection(HttpsEndpointImpl endpoint,
			CallContext context,
			String serverHost,
			int port,
			SocketFactory socketFactory)
	    throws IOException
	{
	    super(context, serverHost, port, socketFactory);
	    this.endpoint = endpoint;
	    HttpSettings settings =
		(HttpSettings) AccessController.doPrivileged(
		    new PrivilegedAction() {
			public Object run() {
			    return HttpSettings.getHttpSettings();
			}
		    });
	    proxyHost = settings.nonProxied(serverHost, false)
		? null : settings.getHttpsProxyHost(null);
	    proxyPort = (proxyHost == null)
		? 0 : settings.getHttpsProxyPort(443);
	}

	/**
	 * Attempts to create a new socket for the specified call context and
	 * cipher suites.
	 *
	 * @throws SSLException if the requested suites cannot be supported
	 * @throws IOException if an I/O failure occurs
	 */
	void establishNewSocket() throws IOException {
	    if (proxyHost == null) {
		httpClient = new HttpClient(serverHost, port, this);
	    } else {
  		httpClient = new HttpClient(
  		    serverHost, port, proxyHost, proxyPort, this);
	    }
	}

 	/**
	 * Uses the HTTPClientConnection to create an OutboundRequest object
	 * with the specified call context, and sets the idle time to 0.
	 */
	OutboundRequest newRequest(CallContext callContext)
	    throws IOException
	{
	    OutboundRequest request = new HttpsOutboundRequest(
		httpClient.newRequest(), this, callContext);
	    idleTime = 0;
	    return request;
	}

	/* inherit javadoc */
	public void close() throws IOException {
	    synchronized (this) {
		if (closed) {
		    return;
		}
	    }
	    super.close();
	    /* Close the associated HTTP connection. */
	    if (httpClient != null) {
		httpClient.shutdown(true);
		httpClient = null;
	    }
	}

	/**
	 * Returns true if the the recorded idle time is more than IDLE_TIMEOUT
	 * milliseconds before now.  If the recorded idle time is zero, sets
	 * the recorded idle time to now.
	 */
	boolean checkIdle(long now) {
	    if (idleTime == 0) {
		idleTime = now;
		return false;
	    } else {
		return now - idleTime > IDLE_TIMEOUT;
	    }
	}

	/**
	 * Adds this connection to the set of idle connections recorded for the
	 * connection's endpoint.
	 */
	void noteIdle() {
	    synchronized (endpointMap) {
		EndpointInfo info =
		    ((EndpointInfo) endpointMap.get(endpoint));
		if (info != null) {
		    info.noteIdle(this);
		}
		idleTime = 0;
	    }
	}

	/* -- Implement HttpClientSocketFactory -- */

	/**
	 * Creates a plain socket to use to talk to the proxy host, else
	 * creates an SSL socket for a direct connection to the server.
	 */
	public Socket createSocket(String host, int port) throws IOException {
	    Socket s = createPlainSocket(host, port);
	    if (proxyHost == null) {
		s = setSSLSocket(
		    (SSLSocket) sslSocketFactory.createSocket(
			s, host, port, true));
	    }
	    return s;
	}

	/**
	 * Creates an SSL socket on top of the one the HTTP code used to
	 * connect through the proxy.
	 */
	public Socket createTunnelSocket(Socket s) throws IOException {
	    return setSSLSocket(
		(SSLSocket) sslSocketFactory.createSocket(
		    s, s.getInetAddress().getHostName(), s.getPort(),
		    true /* autoClose */));
	}

	/**
	 * Stores the new socket in the sslSocket field, does a handshake on
	 * it, and returns it.
	 */
	private Socket setSSLSocket(SSLSocket newSocket) throws IOException {
	    sslSocket = newSocket;
	    establishSuites();
	    return sslSocket;
	}
    }

    /**
     * Subclass of HttpClientConnection that closes the associated connection
     * when it shuts down and moves it to the idle list when it becomes idle.
     */
    private static final class HttpClient extends HttpClientConnection {

	/** The associated secure connection. */
	private final HttpsConnection connection;

	HttpClient(String host, int port, HttpsConnection connection)
	    throws IOException
	{
	    super(host, port, connection, httpClientManager);
	    this.connection = connection;
	}

	HttpClient(String targetHost,
		   int targetPort,
		   String proxyHost,
		   int proxyPort,
		   HttpsConnection connection)
	    throws IOException
	{
	    super(targetHost, targetPort, proxyHost, proxyPort,
		  true /* tunnel */, connection, httpClientManager);
	    this.connection = connection;
	}

	/** Tells the connection that it is idle. */
	protected void idle() {
	    connection.noteIdle();
	}

	/** Closes the associated secure connection. */
	public boolean shutdown(boolean force) {
	    boolean result = super.shutdown(force);
	    if (result) {
		try {
		    connection.close();
		} catch (IOException e) {
		}
	    }
	    return result;
	}
    }

    /**
     * Manages the open connections associated with endpoints equal to a
     * representative endpoint.
     */
    private static final class EndpointInfo {

	/**
	 * A representative endpoint that equals the endpoint for all the
	 * connections.
	 */
	private final HttpsEndpointImpl endpoint;

	/** The idle connections for the endpoint. */
	private final List idle = new ArrayList(1);

	/** The connections that are in use for the endpoint. */
	private final List inUse = new ArrayList(1);

	EndpointInfo(HttpsEndpointImpl endpoint) {
	    this.endpoint = endpoint;
	}

	/**
	 * Chooses and returns an idle connection that satisfies the
	 * constraints, removing the connection from the list of idle
	 * connections and adding it to the list of ones in use, else returns
	 * null.
	 */
	HttpsConnection connect(CallContext context)
	    throws IOException
	{
	    List reap = new ArrayList(0);
	    checkIdle(System.currentTimeMillis(), reap);
	    for (int i = reap.size(); --i >= 0; ) {
		try {
		    ((HttpsConnection) reap.get(i)).close();
		} catch (IOException e) {
		}
	    }
	    HttpsConnection result;
	    synchronized (this) {
		result = (HttpsConnection) endpoint.connect(
		    context, Collections.EMPTY_SET, idle);
		if (result != null) {
		    idle.remove(result);
		} else {
		    result = (HttpsConnection) endpoint.connect(context);
		}
	    }
	    synchronized (endpointMap) {
		EndpointInfo info = (EndpointInfo) endpointMap.get(endpoint);
		if (info == null) {
		    if (endpointMap.isEmpty()) {
			systemExecutor.execute(
			    new Reaper(), "HttpsEndpointReaper");
		    }
		    endpointMap.put(endpoint, this);
		    info = this;
		}
		info.noteInUse(result);
	    }
	    return result;
	}

	/** Adds a connection to the set of connections in use. */
	synchronized void noteInUse(HttpsConnection connection) {
	    inUse.add(connection);
	}

	/** Removes a connection from the set of connections in use. */
	synchronized void noteClosed(HttpsConnection connection) {
	    inUse.remove(connection);
	}

	/**
	 * Removes a connection from the set of connections in use and adds it
	 * to the set of idle connections.
	 */
	synchronized void noteIdle(HttpsConnection connection) {
	    inUse.remove(connection);
	    idle.add(connection);
	}

	/**
	 * For each connection, calls checkIdle on the connection and, if that
	 * returns true, removes the connection and adds it to the reap list.
	 * Returns true if no in-use or idle connections remain.
	 */
	synchronized boolean checkIdle(long now, List reap) {
	    for (int i = idle.size(); --i >= 0; ) {
		HttpsConnection connection = (HttpsConnection) idle.get(i);
		if (connection.checkIdle(now)) {
		    reap.add(connection);
		    idle.remove(connection);
		}
	    }
	    return idle.isEmpty() && inUse.isEmpty();
	}
    }

    /**
     * Implements OutboundRequest using the specified OutboundRequest,
     * HttpsConnection, and OutboundRequestHandle.
     */
    private static final class HttpsOutboundRequest implements OutboundRequest {
	private final OutboundRequest request;
	private final HttpsConnection connection;
	private final OutboundRequestHandle handle;

	HttpsOutboundRequest(OutboundRequest request,
			     HttpsConnection connection,
			     OutboundRequestHandle handle)
	{
	    this.connection = connection;
	    this.request = request;
	    this.handle = handle;
	}

	/* Delegate to connection */
	public void populateContext(Collection context) {
	    connection.populateContext(handle, context);
	}

	/* Delegate to connection */
	public InvocationConstraints getUnfulfilledConstraints() {
	    return connection.getUnfulfilledConstraints(handle);
	}

	/* Delegate to underlying request */
	public OutputStream getRequestOutputStream() {
	    return request.getRequestOutputStream();
	}

	/* Delegate to underlying request */
	public InputStream getResponseInputStream() {
	    return request.getResponseInputStream();
	}

	/* Delegate to underlying request */
	public boolean getDeliveryStatus() {
	    return request.getDeliveryStatus();
	}

	/* Delegate to underlying request */
	public void abort() {
	    request.abort();
	}
    }

    /**
     * Records idle times in connections and shuts them down if they have been
     * idle for at least IDLE_TIMEOUT milliseconds.
     */
    private static final class Reaper implements Runnable {

	/** Non-private constructor to avoid accessor methods. */
	Reaper() { }

	/**
	 * Sleep for IDLE_TIMEOUT milliseconds.  Then call checkIdle on each
	 * connection, and, if that returns true, remove the connection from
	 * the list of idle connections.  Then shutdown all of idle connections
	 * that have been collected.  Terminate if no connections remain, else
	 * wait for the next timeout.
	 */
	public void run() {
	    List reap = new ArrayList(1);
	    boolean done;
	    do {
		try {
		    Thread.sleep(IDLE_TIMEOUT);
		} catch (InterruptedException e) {
		    return;
		}
		long now = System.currentTimeMillis();
		synchronized (endpointMap) {
		    for (Iterator iter = endpointMap.values().iterator();
			 iter.hasNext(); )
		    {
			EndpointInfo info = (EndpointInfo) iter.next();
			if (info.checkIdle(now, reap)) {
			    iter.remove();
			}
		    }
		    done = endpointMap.isEmpty();
		}
		for (int i = reap.size(); --i >= 0; ) {
		    try {
			((HttpsConnection) reap.get(i)).close();
		    } catch (IOException e) {
		    }
		}
		reap.clear();
	    } while (!done);
	}
    }
}
