/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.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://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.jini.jeri.ssl;
import com.sun.jini.jeri.internal.connection.BasicConnManagerFactory;
import com.sun.jini.jeri.internal.connection.ConnManager;
import com.sun.jini.jeri.internal.connection.ConnManagerFactory;
import com.sun.jini.jeri.internal.runtime.Util;
import com.sun.jini.logging.Levels;
import com.sun.jini.logging.LogUtil;
import java.io.IOException;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.Principal;
import java.security.PrivilegedAction;
import java.security.cert.CertPath;
import java.security.cert.X509Certificate;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.SocketFactory;
import javax.security.auth.Subject;
import javax.security.auth.x500.X500Principal;
import javax.security.auth.x500.X500PrivateCredential;
import net.jini.core.constraint.InvocationConstraints;
import net.jini.io.UnsupportedConstraintException;
import net.jini.jeri.Endpoint;
import net.jini.jeri.OutboundRequest;
import net.jini.jeri.OutboundRequestIterator;
import net.jini.jeri.connection.Connection;
import net.jini.jeri.connection.ConnectionEndpoint;
import net.jini.jeri.connection.OutboundRequestHandle;
import net.jini.security.AuthenticationPermission;
/**
* Provides the implementation of SslEndpoint so that the implementation can be
* inherited by HttpsEndpoint without revealing the inheritance in the public
* API.
*
* @author Sun Microsystems, Inc.
*/
class SslEndpointImpl extends Utilities implements ConnectionEndpoint {
/* -- Fields -- */
/** Client logger */
static final Logger logger = clientLogger;
/**
* Weak key map that maps connection endpoints to weak references to the
* associated ConnManager. The weak values insure that the keys will not
* be strongly held, since the connection manager has a strong reference to
* the associated connection endpoint.
*/
private static final Map connectionMgrs = new WeakHashMap();
/** The size of the connection context cache. */
private static final int CACHE_SIZE = 4;
/** The factory for default connection managers. */
private static final ConnManagerFactory connectionManagerFactory =
new BasicConnManagerFactory();
/** The associated endpoint. */
final Endpoint endpoint;
/** The name of the server host. */
final String serverHost;
/** The server port. */
final int port;
/** The factory for creating sockets, or null to use default sockets. */
final SocketFactory socketFactory;
/**
* Whether to disable calling Socket.connect -- set when used by discovery
* providers.
*/
boolean disableSocketConnect;
/** A cache for recently computed connection contexts. */
private ConnectionContextCache[] connectionContextCache =
new ConnectionContextCache[CACHE_SIZE];
/** Next index for a connectionContextCache miss; counts down, not up. */
private int cacheNext;
/** The connection manager for this endpoint or null if not yet set. */
ConnManager connectionManager;
/* -- Constructors -- */
/** Creates an instance of this class. */
SslEndpointImpl(Endpoint endpoint,
String serverHost,
int port,
SocketFactory socketFactory)
{
this.endpoint = endpoint;
if (serverHost == null) {
throw new NullPointerException("serverHost is null");
}
this.serverHost = serverHost;
if (port <= 0 || port > 0xFFFF) {
throw new IllegalArgumentException("Invalid port: " + port);
}
this.port = port;
this.socketFactory = socketFactory;
}
/* -- Methods -- */
/** Returns a string representation of this object. */
public String toString() {
return getClassName(this) + fieldsToString();
}
/** Returns a string representation of the fields of this object. */
final String fieldsToString() {
return "[" + serverHost + ":" + port +
(socketFactory != null ? ", " + socketFactory : "") + "]";
}
/* -- Implement Endpoint -- */
/** Returns a hash code value for this object. */
public int hashCode() {
return getClass().hashCode()
^ serverHost.hashCode()
^ port
^ (socketFactory != null ? socketFactory.hashCode() : 0);
}
/**
* Two instances of this class are equal if they have the same actual
* class; have the same values for server host and port; and have socket
* factories that are either both null, or have the same actual class and
* are equal.
*/
public boolean equals(Object object) {
if (object == null || object.getClass() != getClass()) {
return false;
}
SslEndpointImpl other = (SslEndpointImpl) object;
return serverHost.equals(other.serverHost)
&& port == other.port
&& Util.sameClassAndEquals(socketFactory, other.socketFactory);
}
/** Implements Endpoint.newRequest */
final OutboundRequestIterator newRequest(
InvocationConstraints constraints)
{
if (constraints == null) {
throw new NullPointerException("Constraints cannot be null");
}
try {
return newRequest(getCallContext(constraints));
} catch (UnsupportedConstraintException e) {
return new ExceptionOutboundRequestIterator(e);
} catch (SecurityException e) {
return new ExceptionOutboundRequestIterator(e);
}
}
/**
* An outbound request iterator that throws an IOException or a
* SecurityException.
*/
private static final class ExceptionOutboundRequestIterator
implements OutboundRequestIterator
{
private final Exception exception;
private boolean done = false;
ExceptionOutboundRequestIterator(Exception exception) {
this.exception = exception;
}
public synchronized boolean hasNext() {
return !done;
}
public synchronized OutboundRequest next() throws IOException {
if (done) {
throw new NoSuchElementException();
}
done = true;
if (exception instanceof SecurityException) {
throw (SecurityException) exception;
} else {
throw (IOException) exception;
}
}
}
/** Implements Endpoint.newRequest when the constraints are supported. */
OutboundRequestIterator newRequest(CallContext callContext) {
return getConnectionManager().newRequest(callContext);
}
/** Returns the connection manager for this endpoint. */
private ConnManager getConnectionManager() {
synchronized (connectionMgrs) {
if (connectionManager == null) {
Reference ref = (Reference) connectionMgrs.get(this);
connectionManager =
(ref != null) ? (ConnManager) ref.get() : null;
if (connectionManager == null) {
connectionManager = connectionManagerFactory.create(this);
connectionMgrs.put(
this, new WeakReference(connectionManager));
}
}
return connectionManager;
}
}
/**
* Returns a context for making a remote call with the specified
* constraints and the current subject. This method does not perform
* communication with the remote server.
*
* Throws a SecurityException if lack of authentication permissions
* needed for requirements prevent the use of all contexts.
*
* Throws UnsupportedConstraintException if some requirements cannot
* be satisfied.
*
* Returns a CallContext if throwing an exception or returning null
* would reveal information about the current subject that the
* caller does not have permission to know.
*/
private CallContext getCallContext(InvocationConstraints constraints)
throws UnsupportedConstraintException
{
final AccessControlContext acc = AccessController.getContext();
Subject clientSubject = (Subject) AccessController.doPrivileged(
new PrivilegedAction() {
public Object run() {
return Subject.getSubject(acc);
}
});
Set clientPrincipals = getClientPrincipals(constraints.requirements());
boolean requiredClient = clientPrincipals != null;
boolean constrainedServer = getServerPrincipals(constraints) != null;
Boolean getSubject = null;
if (!requiredClient) {
/* Try using principals from Subject instead */
if (clientSubject == null) {
clientPrincipals = Collections.EMPTY_SET;
} else {
/*
* XXX: Work around BugID 4892841, Subject.getPrincipals(Class)
* not thread-safe against changes to principals.
* -tjb[18.Jul.2003]
*/
synchronized (clientSubject.getPrincipals()) {
clientPrincipals =
clientSubject.getPrincipals(X500Principal.class);
}
}
if (clientPrincipals.isEmpty()) {
getSubject = getSubjectPermitted();
if (getSubject == Boolean.FALSE) {
/* Don't reveal that the client Subject has no principals.
* Provide a dummy Principal (no credentials) which cannot
* be authenticated in order to follow the same code path
* as if there were a Subject principal.
*/
clientPrincipals =
Collections.singleton(UNKNOWN_PRINCIPAL);
}
}
}
/* Compute a list of ConnectionContexts between principals (as above):
* (a) principals named in client constraints, if any; otherwise
* (b) principals named in the client Subject, if any; otherwise
* (c) a dummy principal or no principal
* and the principals named in server constraints (if any). These
* contexts will satisfy most constraints.
*/
List contexts = new CopyOnRemoveList(
getConnectionContexts(constraints, clientPrincipals));
if (constrainedServer) {
/* Server prinicipals were named in constraints. Remove from the
* context list any ConnectionContexts for which there is no
* permission to authenticate the context's client principal with
* the context's server principal.
*/
try {
contexts = checkAuthenticationPermissions(contexts);
} catch (SecurityException e) {
/* This SecurityException indicates that there are no
* remaining valid ConnectionContexts, either because there
* are no client constraint principals and no client subject
* principals, or because none of these principals was given
* permission to authenticate with any server constraint
* principal.
*
* If there were no client constraint principals, then
* throwing an exception here would reveal that at least one
* client subject principal existed and did not have
* authentication permission.
*
* If there were client constraint principals, or if the
* caller could determine Subject principals for itself, then
* pass on the SecurityException.
*/
if (!requiredClient && getSubject == null) {
getSubject = getSubjectPermitted();
}
if (requiredClient || getSubject == Boolean.TRUE) {
if (logger.isLoggable(Levels.FAILED)) {
logThrow(
logger, Levels.FAILED,
SslEndpointImpl.class, "getCallContext",
"new request for {0}\nwith {1}\nand {2}\nthrows",
new Object[] { endpoint, constraints,
subjectString(clientSubject) },
e);
}
throw e;
} else {
/* Don't reveal that the client Subject has no principals.
* Provide a dummy Principal (no credentials) which cannot
* be authenticated in order to follow the same code path
* as if there were a Subject principal.
*/
CallContext result = createCallContext(
getConnectionContexts(
constraints,
Collections.singleton(UNKNOWN_PRINCIPAL)),
null);
if (logger.isLoggable(Levels.FAILED)) {
logThrow(
logger, Levels.FAILED,
SslEndpointImpl.class, "getCallContext",
"new request for {0}\nwith {1}\nand {2}\n" +
"will fail but cannot throw " +
"because caller has no subject access\n" +
"returns {3}\n" +
"caught exception",
new Object[] { endpoint, constraints,
subjectString(clientSubject),
result },
e);
}
return result;
}
}
}
UnsupportedConstraintException unsupported = null;
if (contexts.isEmpty()) {
unsupported = new UnsupportedConstraintException(
"Constraints not supported: " + constraints);
} else {
boolean checkSubject;
if (constrainedServer) {
checkSubject = true;
} else {
if (getSubject == null) {
getSubject = getSubjectPermitted();
}
checkSubject = (getSubject == Boolean.TRUE);
}
if (checkSubject) {
/* Check subject if caller has any access */
try {
contexts = checkSubject(contexts, clientSubject,
constrainedServer, constraints);
} catch (UnsupportedConstraintException e) {
unsupported = e;
}
}
}
if (unsupported != null) {
if (logger.isLoggable(Levels.FAILED)) {
logThrow(logger, Levels.FAILED,
SslEndpointImpl.class, "getCallContext",
"new request for {0}\nwith {1}\nand {2}\nthrows",
new Object[] { endpoint, constraints,
subjectString(clientSubject) },
unsupported);
}
throw unsupported;
} else {
CallContext result = createCallContext(contexts, clientSubject);
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE,
"new request for {0}\nwith {1}\nand {2}\n" +
"returns {3}",
new Object[] {
endpoint, constraints,
subjectString(clientSubject), result
});
}
return result;
}
}
/** Convert connection contexts to a call context */
private CallContext createCallContext(List contexts,
Subject clientSubject)
{
boolean clientAuthRequired = true;
boolean clientAuthPermitted = false;
Set clientPrincipals = null;
Set serverPrincipals = null;
List suites = new ArrayList();
boolean integrityRequired = false;
boolean integrityPreferred = false;
long connectionTimeout = -1;
int max = contexts.size();
for (int i = 0; i < max; i++) {
ConnectionContext context = (ConnectionContext) contexts.get(i);
if (context.client == null) {
clientAuthRequired = false;
} else {
clientAuthPermitted = true;
if (clientPrincipals == null) {
clientPrincipals = new HashSet();
}
/*
* Don't include the unknown principal, but still should record
* that principals were specified by making clientPrincipals
* non-null.
*/
if (context.client != UNKNOWN_PRINCIPAL) {
clientPrincipals.add(context.client);
}
}
if (context.server != null &&
context.server != UNKNOWN_PRINCIPAL)
{
if (serverPrincipals == null) {
serverPrincipals = new HashSet();
}
serverPrincipals.add(context.server);
}
if (!suites.contains(context.cipherSuite)) {
suites.add(context.cipherSuite);
}
if (context.getIntegrityRequired()) {
integrityRequired = true;
} else if (context.getIntegrityPreferred()) {
integrityPreferred = true;
}
if (context.getConnectionTime() != -1 &&
(connectionTimeout == -1 ||
connectionTimeout > context.getConnectionTime()))
{
connectionTimeout = context.getConnectionTime();
}
}
return new CallContext(
endpoint, this,
clientAuthPermitted ? clientSubject : null,
clientAuthRequired, clientPrincipals, serverPrincipals, suites,
integrityRequired, integrityPreferred, connectionTimeout);
}
/**
* Returns a list of the contexts which are supported by principals and
* credentials in the Subject. Throws an UnsupportedConstraintException if
* none of the contexts are supported, otherwise returns a non-empty list.
*/
private static List checkSubject(List contexts,
Subject clientSubject,
boolean constrainedServer,
InvocationConstraints constraints)
throws UnsupportedConstraintException
{
Map publicCreds = getPublicCredentials(clientSubject);
X500PrivateCredential[] privateCreds =
(clientSubject != null && constrainedServer)
? (X500PrivateCredential[]) AccessController.doPrivileged(
new SubjectCredentials.GetAllPrivateCredentialsAction(
clientSubject))
: new X500PrivateCredential[0];
Set missingPublic = new HashSet();
Set missingPrivate = new HashSet();
/* Check principals and credentials */
top:
for (int i = contexts.size(); --i >= 0; ) {
ConnectionContext context = (ConnectionContext) contexts.get(i);
if (context.client == null) {
continue; /* Anonymous client OK */
}
Collection certs = (Collection) publicCreds.get(context.client);
if (certs == null) {
logger.log(Levels.HANDLED,
"missing principal or public credentials: {0}",
context.client);
contexts.remove(i);
missingPublic.add(context.client);
} else if (constrainedServer) {
/*
* Checked authentication permissions, so OK to check private
* credentials.
*/
for (int j = privateCreds.length; --j >= 0; ) {
X509Certificate cert = privateCreds[j].getCertificate();
if (cert != null) { /* null if destroyed */
if (certs.contains(cert)) {
continue top; /* Private credentials OK */
}
}
}
logger.log(Levels.HANDLED, "missing private credentials: {0}",
context.client);
contexts.remove(i);
missingPrivate.add(context.client);
}
}
if (!contexts.isEmpty()) {
return contexts;
} else {
throw new UnsupportedConstraintException(
"Constraints not supported: " + constraints + ";" +
(missingPublic.isEmpty() ? ""
: ("\nmissing principals or public credentials: " +
missingPublic)) +
(missingPrivate.isEmpty() ? ""
: "\nmissing private credentials: " + missingPrivate));
}
}
/**
* Checks if the caller has permission to get the current subject,
* returning Boolean.TRUE or FALSE.
*/
private static Boolean getSubjectPermitted() {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
try {
sm.checkPermission(getSubjectPermission);
} catch (SecurityException e) {
return Boolean.FALSE;
}
}
return Boolean.TRUE;
}
/**
* Removes the contexts for which the client does not have authentication
* permission. Throws SecurityException if lack of permission prevents any
* contexts from being used.
*/
private static List checkAuthenticationPermissions(List contexts) {
if (contexts.isEmpty()) {
return contexts;
}
SecurityManager sm = System.getSecurityManager();
if (sm == null) {
return contexts;
}
Map perms = new HashMap();
Set exceptions = new HashSet();
for (int i = contexts.size(); -- i >= 0; ) {
ConnectionContext context = (ConnectionContext) contexts.get(i);
if (context.client == null) {
/* Client anonymous -- OK */
continue;
} else if (context.server == UNKNOWN_PRINCIPAL) {
/* Server not known -- can't check any permissions */
break;
}
AuthenticationPermission p = new AuthenticationPermission(
Collections.singleton(context.client),
Collections.singleton(context.server),
"connect");
Object value = perms.get(p);
if (Boolean.FALSE.equals(value)) {
contexts.remove(i);
} else if (!Boolean.TRUE.equals(value)) {
try {
sm.checkPermission(p);
perms.put(p, Boolean.TRUE);
} catch (SecurityException e) {
logger.log(
Levels.HANDLED,
"check authentication permission caught exception",
e);
perms.put(p, Boolean.FALSE);
exceptions.add(e);
contexts.remove(i);
}
}
}
if (!contexts.isEmpty()) {
return contexts;
} else if (exceptions.size() == 1) {
throw (SecurityException) exceptions.iterator().next();
} else {
throw new SecurityException(exceptions.toString());
}
}
/**
* Returns a map that maps each principal in the subject to a set of the
* associated X.500 public credentials.
*/
private static Map getPublicCredentials(Subject subject) {
Map publicCreds = new HashMap();
List certPaths = SubjectCredentials.getCertificateChains(subject);
if (certPaths != null) {
for (int i = certPaths.size(); --i >= 0; ) {
CertPath chain = (CertPath) certPaths.get(i);
X509Certificate cert = SubjectCredentials.firstX509Cert(chain);
X500Principal p =
SubjectCredentials.getPrincipal(subject, cert);
if (p != null) {
Collection certs = (Collection) publicCreds.get(p);
if (certs == null) {
certs = new ArrayList(1);
publicCreds.put(p, certs);
}
certs.add(cert);
}
}
}
return publicCreds;
}
/**
* A List that supports removing items by making a copy of the underlying
* list.
*/
private static final class CopyOnRemoveList extends AbstractList {
private List list;
private boolean modified;
CopyOnRemoveList(List list) {
this.list = list;
}
public Object get(int index) {
return list.get(index);
}
public int size() {
return list.size();
}
public Object remove(int index) {
if (!modified) {
list = new ArrayList(list);
modified = true;
}
return list.remove(index);
}
}
/**
* Defines a structure to cache a ConnectionContexts for specific
* constraints and client principals.
*/
private static final class ConnectionContextCache {
final InvocationConstraints constraints;
final Set clientPrincipals;
final List connectionContexts;
ConnectionContextCache(InvocationConstraints constraints,
Set clientPrincipals,
List connectionContexts)
{
this.constraints = constraints;
this.clientPrincipals = clientPrincipals;
this.connectionContexts = connectionContexts;
}
}
/**
* Gets an unmodifiable list of the ConnectionContexts for the
* specified constraints and client principals.
*/
private List getConnectionContexts(InvocationConstraints constraints,
Set clientPrincipals)
{
synchronized (connectionContextCache) {
for (int i = CACHE_SIZE; --i >= 0; ) {
ConnectionContextCache cache = connectionContextCache[i];
if (cache != null &&
cache.constraints.equals(constraints) &&
cache.clientPrincipals.equals(clientPrincipals))
{
logger.log(Level.FINEST, "used connection cache");
return cache.connectionContexts;
}
}
}
Set serverPrincipals = getServerPrincipals(constraints);
if (serverPrincipals == null) {
serverPrincipals = Collections.singleton(UNKNOWN_PRINCIPAL);
}
List contexts = Collections.unmodifiableList(
computeConnectionContexts(
getSupportedCipherSuites(), clientPrincipals, serverPrincipals,
constraints));
synchronized (connectionContextCache) {
connectionContextCache[cacheNext] =
new ConnectionContextCache(
constraints, clientPrincipals, contexts);
if (cacheNext == 0) {
cacheNext = CACHE_SIZE;
}
cacheNext--;
}
return contexts;
}
/** Used for sorting ConnectionContexts by preferences and suite order. */
private static final class ComparableConnectionContext
implements Comparable
{
final ConnectionContext context;
private final int suiteIndex;
ComparableConnectionContext(ConnectionContext context,
int suiteIndex)
{
this.context = context;
this.suiteIndex = suiteIndex;
}
public int compareTo(Object object) {
ComparableConnectionContext other =
(ComparableConnectionContext) object;
/* Lower value for more preferences */
int result =
other.context.getPreferences() - context.getPreferences();
if (result == 0) {
/* Lower value for lower index */
result = suiteIndex - other.suiteIndex;
}
return result;
}
public String toString() {
StringBuffer sb = new StringBuffer("ComparableConnectionContext[");
context.fieldsToString(sb);
sb.append(", index: ").append(suiteIndex);
sb.append("]");
return sb.toString();
}
}
/**
* Computes a list of ConnectionContexts for the specified set of
* suites, client and server principals, and constraints, sorted by
* preferences and suite order.
*/
private static List computeConnectionContexts(
String[] suites,
Set clients,
Set servers,
InvocationConstraints constraints)
{
List result = new ArrayList(
suites.length * (clients.size() + 1) * (servers.size() + 1));
for (int suiteIndex = suites.length; --suiteIndex >= 0; ) {
String suite = suites[suiteIndex];
Iterator cIter = clients.iterator();
Principal client;
do {
if (cIter.hasNext()) {
client = (Principal) cIter.next();
assert client != null;
} else {
client = null;
}
Iterator sIter = servers.iterator();
Principal server;
do {
if (sIter.hasNext()) {
server = (Principal) sIter.next();
assert server != null;
} else {
server = null;
}
for (int i = 2; --i >= 0; ) {
boolean integrity = i == 0;
ConnectionContext context =
ConnectionContext.getInstance(
suite, client, server, integrity,
true /* clientSide */, constraints);
if (context != null) {
result.add(
new ComparableConnectionContext(
context, suiteIndex));
}
}
} while (server != null);
} while (client != null);
}
Collections.sort(result);
logger.log(Level.FINEST, "compute connection contexts produces {0}",
result);
for (int i = result.size(); --i >= 0; ) {
ComparableConnectionContext ccc =
(ComparableConnectionContext) result.get(i);
result.set(i, ccc.context);
}
return result;
}
/* -- Implement ConnectionEndpoint -- */
/** Creates a new connection. */
public Connection connect(OutboundRequestHandle handle) throws IOException {
SslConnection connection = new SslConnection(
CallContext.coerce(handle, endpoint), serverHost, port,
socketFactory);
connection.establishCallContext();
return connection;
}
/** Chooses a connection from existing connections. */
public Connection connect(OutboundRequestHandle handle,
Collection active,
Collection idle)
{
CallContext context = CallContext.coerce(handle, endpoint);
if (active == null || idle == null) {
throw new NullPointerException("Arguments cannot be null");
}
Connection result = null;
/*
* Choose the first connection with an appropriate subject, an active
* suite that is one of the requested suites, and one for which all the
* requested suites better than the active one were also better for its
* call context. That insures that we've gotten the best connection we
* can, assuming a new handshake were to make similar decisions.
*/
boolean checkedResolvePermission = false;
for (Iterator iter = new ConnectionsIterator(endpoint, active, idle);
iter.hasNext(); )
{
SslConnection connection = (SslConnection) iter.next();
if (connection.useFor(context)) {
String phost = connection.getProxyHost();
boolean usingProxy = (phost.length() != 0);
if (usingProxy) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
try {
sm.checkConnect(serverHost, port);
} catch (SecurityException e) {
if (logger.isLoggable(Levels.FAILED)) {
logThrow(logger, Levels.FAILED,
SslEndpointImpl.class, "connect",
"choose connection for {0}\nthrows",
new Object[] { this }, e);
}
throw e;
}
}
} else {
if (!checkedResolvePermission) {
try {
checkResolvePermission();
} catch (SecurityException e) {
if (logger.isLoggable(Levels.FAILED)) {
LogUtil.logThrow(logger, Levels.FAILED,
SslEndpointImpl.class, "connect",
"exception resolving host {0}",
new Object[] { serverHost }, e);
}
throw e;
}
checkedResolvePermission = true;
}
try {
connection.checkConnectPermission();
} catch (SecurityException e) {
if (logger.isLoggable(Levels.HANDLED)) {
LogUtil.logThrow(logger, Levels.HANDLED,
SslEndpointImpl.class, "nextRequest",
"access to reuse connection {0} denied",
new Object[] { connection }, e);
}
continue;
}
}
result = connection;
break;
}
}
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE,
"choose connection for {0}\nwith active {1}\n" +
"and idle {2}\nreturns {3}",
new Object[] { handle, active, idle, result });
}
return result;
}
private void checkResolvePermission() {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkConnect(serverHost, -1);
}
}
/**
* Defines an iterator over active and idle connections which performs
* error checking on connections.
*/
private static final class ConnectionsIterator implements Iterator {
private final Endpoint endpoint;
private Collection active; /* Set to null when exhausted */
private final Collection idle;
private Iterator iter;
ConnectionsIterator(Endpoint endpoint,
Collection active,
Collection idle)
{
this.endpoint = endpoint;
this.active = active;
this.idle = idle;
iter = active.iterator();
if (!iter.hasNext()) {
this.active = null;
iter = idle.iterator();
}
}
public boolean hasNext() {
return iter.hasNext();
}
public Object next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
Object next = iter.next();
if (next == null) {
throw new NullPointerException("Connection cannot be null");
} else if (!(next instanceof SslConnection)) {
throw new IllegalArgumentException(
"Connection must be of type SslConnection: " + next);
}
SslConnection result = (SslConnection) next;
if (!endpoint.equals(result.callContext.endpoint)) {
throw new IllegalArgumentException(
"Connection has wrong endpoint: found " +
result.callContext.endpoint + ", expected " + endpoint);
}
if (!iter.hasNext() && active != null) {
active = null;
iter = idle.iterator();
}
return result;
}
public void remove() {
throw new UnsupportedOperationException();
}
}
}