com.eviware.soapui.impl.wsdl.support.http.SoapUIMultiThreadedHttpConnectionManager Maven / Gradle / Ivy
/*
* soapUI, copyright (C) 2004-2011 smartbear.com
*
* soapUI is free software; you can redistribute it and/or modify it under the
* terms of version 2.1 of the GNU Lesser General Public License as published by
* the Free Software Foundation.
*
* soapUI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details at gnu.org.
*/
package com.eviware.soapui.impl.wsdl.support.http;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.WeakHashMap;
import org.apache.commons.httpclient.ConnectionPoolTimeoutException;
import org.apache.commons.httpclient.HostConfiguration;
import org.apache.commons.httpclient.HttpConnection;
import org.apache.commons.httpclient.HttpConnectionManager;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.params.HttpConnectionManagerParams;
import org.apache.commons.httpclient.params.HttpConnectionParams;
import org.apache.commons.httpclient.protocol.Protocol;
import org.apache.commons.httpclient.util.IdleConnectionHandler;
import org.apache.log4j.Logger;
/**
* Manages a set of HttpConnections for various HostConfigurations. Modified to
* keep different pools for different keystores.
*
* @author Michael Becke
* @author Eric Johnson
* @author Mike Bowler
* @author Carl A. Dunham
*
* @since 2.0
*/
public class SoapUIMultiThreadedHttpConnectionManager implements HttpConnectionManager
{
// -------------------------------------------------------- Class Variables
/** Log object for this class. */
private static final Logger LOG = Logger.getLogger( SoapUIMultiThreadedHttpConnectionManager.class );
/** The default maximum number of connections allowed per host */
public static final int DEFAULT_MAX_HOST_CONNECTIONS = 2; // Per RFC 2616 sec
// 8.1.4
/** The default maximum number of connections allowed overall */
public static final int DEFAULT_MAX_TOTAL_CONNECTIONS = 20;
/**
* A mapping from Reference to ConnectionSource. Used to reclaim resources
* when connections are lost to the garbage collector.
*/
private static final Map REFERENCE_TO_CONNECTION_SOURCE = new HashMap();
/**
* The reference queue used to track when HttpConnections are lost to the
* garbage collector
*/
private static final ReferenceQueue REFERENCE_QUEUE = new ReferenceQueue();
/**
* The thread responsible for handling lost connections.
*/
private static ReferenceQueueThread REFERENCE_QUEUE_THREAD;
/**
* Holds references to all active instances of this class.
*/
private static WeakHashMap ALL_CONNECTION_MANAGERS = new WeakHashMap();
// ---------------------------------------------------------- Class Methods
/**
* Shuts down and cleans up resources used by all instances of
* MultiThreadedHttpConnectionManager. All static resources are released, all
* threads are stopped, and {@link #shutdown()} is called on all live
* instances of MultiThreadedHttpConnectionManager.
*
* @see #shutdown()
*/
public static void shutdownAll()
{
synchronized( REFERENCE_TO_CONNECTION_SOURCE )
{
// shutdown all connection managers
synchronized( ALL_CONNECTION_MANAGERS )
{
// Don't use an iterator here. Iterators on WeakHashMap can
// get ConcurrentModificationException on garbage collection.
SoapUIMultiThreadedHttpConnectionManager[] connManagers = ( SoapUIMultiThreadedHttpConnectionManager[] )ALL_CONNECTION_MANAGERS
.keySet().toArray( new SoapUIMultiThreadedHttpConnectionManager[ALL_CONNECTION_MANAGERS.size()] );
// The map may shrink after size() is called, or some entry
// may get GCed while the array is built, so expect null.
for( int i = 0; i < connManagers.length; i++ )
{
if( connManagers[i] != null )
connManagers[i].shutdown();
}
}
// shutdown static resources
if( REFERENCE_QUEUE_THREAD != null )
{
REFERENCE_QUEUE_THREAD.shutdown();
REFERENCE_QUEUE_THREAD = null;
}
REFERENCE_TO_CONNECTION_SOURCE.clear();
}
}
/**
* Stores the reference to the given connection along with the host config
* and connection pool. These values will be used to reclaim resources if the
* connection is lost to the garbage collector. This method should be called
* before a connection is released from the connection manager.
*
*
* A static reference to the connection manager will also be stored. To
* ensure that the connection manager can be GCed
* {@link #removeReferenceToConnection(HttpConnection)} should be called for
* all connections that the connection manager is storing a reference to.
*
*
* @param connection
* the connection to create a reference for
* @param hostConfiguration
* the connection's host config
* @param connectionPool
* the connection pool that created the connection
*
* @see #removeReferenceToConnection(HttpConnection)
*/
private static void storeReferenceToConnection( HttpConnectionWithReference connection,
HostConfiguration hostConfiguration, ConnectionPool connectionPool )
{
ConnectionSource source = new ConnectionSource();
source.connectionPool = connectionPool;
source.hostConfiguration = hostConfiguration;
synchronized( REFERENCE_TO_CONNECTION_SOURCE )
{
// start the reference queue thread if needed
if( REFERENCE_QUEUE_THREAD == null )
{
REFERENCE_QUEUE_THREAD = new ReferenceQueueThread();
REFERENCE_QUEUE_THREAD.start();
}
REFERENCE_TO_CONNECTION_SOURCE.put( connection.reference, source );
}
}
/**
* Closes and releases all connections currently checked out of the given
* connection pool.
*
* @param connectionPool
* the connection pool to shutdown the connections for
*/
private static void shutdownCheckedOutConnections( ConnectionPool connectionPool )
{
// keep a list of the connections to be closed
ArrayList connectionsToClose = new ArrayList();
synchronized( REFERENCE_TO_CONNECTION_SOURCE )
{
Iterator referenceIter = REFERENCE_TO_CONNECTION_SOURCE.keySet().iterator();
while( referenceIter.hasNext() )
{
Reference ref = ( Reference )referenceIter.next();
ConnectionSource source = ( ConnectionSource )REFERENCE_TO_CONNECTION_SOURCE.get( ref );
if( source.connectionPool == connectionPool )
{
referenceIter.remove();
HttpConnection connection = ( HttpConnection )ref.get();
if( connection != null )
{
connectionsToClose.add( connection );
}
}
}
}
// close and release the connections outside of the synchronized block to
// avoid holding the lock for too long
for( Iterator i = connectionsToClose.iterator(); i.hasNext(); )
{
HttpConnection connection = ( HttpConnection )i.next();
connection.close();
// remove the reference to the connection manager. this ensures
// that the we don't accidentally end up here again
connection.setHttpConnectionManager( null );
connection.releaseConnection();
}
}
/**
* Removes the reference being stored for the given connection. This method
* should be called when the connection manager again has a direct reference
* to the connection.
*
* @param connection
* the connection to remove the reference for
*
* @see #storeReferenceToConnection(HttpConnection, HostConfiguration,
* ConnectionPool)
*/
private static void removeReferenceToConnection( HttpConnectionWithReference connection )
{
synchronized( REFERENCE_TO_CONNECTION_SOURCE )
{
REFERENCE_TO_CONNECTION_SOURCE.remove( connection.reference );
}
}
// ----------------------------------------------------- Instance Variables
/**
* Collection of parameters associated with this connection manager.
*/
private HttpConnectionManagerParams params = new HttpConnectionManagerParams();
/** Connection Pool */
private ConnectionPool connectionPool;
private volatile boolean shutdown = false;
// ----------------------------------------------------------- Constructors
/**
* No-args constructor
*/
public SoapUIMultiThreadedHttpConnectionManager()
{
this.connectionPool = new ConnectionPool();
synchronized( ALL_CONNECTION_MANAGERS )
{
ALL_CONNECTION_MANAGERS.put( this, null );
}
}
// ------------------------------------------------------- Instance Methods
/**
* Shuts down the connection manager and releases all resources. All
* connections associated with this class will be closed and released.
*
*
* The connection manager can no longer be used once shut down.
*
*
* Calling this method more than once will have no effect.
*/
public synchronized void shutdown()
{
synchronized( connectionPool )
{
if( !shutdown )
{
shutdown = true;
connectionPool.shutdown();
}
}
}
/**
* Gets the staleCheckingEnabled value to be set on HttpConnections that are
* created.
*
* @return true
if stale checking will be enabled on
* HttpConnections
*
* @see HttpConnection#isStaleCheckingEnabled()
*
* @deprecated Use
* {@link HttpConnectionManagerParams#isStaleCheckingEnabled()},
* {@link HttpConnectionManager#getParams()}.
*/
public boolean isConnectionStaleCheckingEnabled()
{
return this.params.isStaleCheckingEnabled();
}
/**
* Sets the staleCheckingEnabled value to be set on HttpConnections that are
* created.
*
* @param connectionStaleCheckingEnabled
* true
if stale checking will be enabled on
* HttpConnections
*
* @see HttpConnection#setStaleCheckingEnabled(boolean)
*
* @deprecated Use
* {@link HttpConnectionManagerParams#setStaleCheckingEnabled(boolean)}
* , {@link HttpConnectionManager#getParams()}.
*/
public void setConnectionStaleCheckingEnabled( boolean connectionStaleCheckingEnabled )
{
this.params.setStaleCheckingEnabled( connectionStaleCheckingEnabled );
}
/**
* Sets the maximum number of connections allowed for a given
* HostConfiguration. Per RFC 2616 section 8.1.4, this value defaults to 2.
*
* @param maxHostConnections
* the number of connections allowed for each hostConfiguration
*
* @deprecated Use
* {@link HttpConnectionManagerParams#setDefaultMaxConnectionsPerHost(int)}
* , {@link HttpConnectionManager#getParams()}.
*/
public void setMaxConnectionsPerHost( int maxHostConnections )
{
this.params.setDefaultMaxConnectionsPerHost( maxHostConnections );
}
/**
* Gets the maximum number of connections allowed for a given
* hostConfiguration.
*
* @return The maximum number of connections allowed for a given
* hostConfiguration.
*
* @deprecated Use
* {@link HttpConnectionManagerParams#getDefaultMaxConnectionsPerHost()}
* , {@link HttpConnectionManager#getParams()}.
*/
public int getMaxConnectionsPerHost()
{
return this.params.getDefaultMaxConnectionsPerHost();
}
/**
* Sets the maximum number of connections allowed for this connection
* manager.
*
* @param maxTotalConnections
* the maximum number of connections allowed
*
* @deprecated Use
* {@link HttpConnectionManagerParams#setMaxTotalConnections(int)}
* , {@link HttpConnectionManager#getParams()}.
*/
public void setMaxTotalConnections( int maxTotalConnections )
{
this.params.setMaxTotalConnections( maxTotalConnections );
}
/**
* Gets the maximum number of connections allowed for this connection
* manager.
*
* @return The maximum number of connections allowed
*
* @deprecated Use
* {@link HttpConnectionManagerParams#getMaxTotalConnections()},
* {@link HttpConnectionManager#getParams()}.
*/
public int getMaxTotalConnections()
{
return this.params.getMaxTotalConnections();
}
/**
* @see HttpConnectionManager#getConnection(HostConfiguration)
*/
public HttpConnection getConnection( HostConfiguration hostConfiguration )
{
while( true )
{
try
{
return getConnectionWithTimeout( hostConfiguration, 0 );
}
catch( ConnectionPoolTimeoutException e )
{
// we'll go ahead and log this, but it should never happen.
// HttpExceptions
// are only thrown when the timeout occurs and since we have no
// timeout
// it should never happen.
LOG.debug( "Unexpected exception while waiting for connection", e );
}
}
}
/**
* Gets a connection or waits if one is not available. A connection is
* available if one exists that is not being used or if fewer than
* maxHostConnections have been created in the connectionPool, and fewer than
* maxTotalConnections have been created in all connectionPools.
*
* @param hostConfiguration
* The host configuration specifying the connection details.
* @param timeout
* the number of milliseconds to wait for a connection, 0 to wait
* indefinitely
*
* @return HttpConnection an available connection
*
* @throws HttpException
* if a connection does not become available in 'timeout'
* milliseconds
*
* @since 3.0
*/
public HttpConnection getConnectionWithTimeout( HostConfiguration hostConfiguration, long timeout )
throws ConnectionPoolTimeoutException
{
LOG.trace( "enter HttpConnectionManager.getConnectionWithTimeout(HostConfiguration, long)" );
if( hostConfiguration == null )
{
throw new IllegalArgumentException( "hostConfiguration is null" );
}
if( LOG.isDebugEnabled() )
{
LOG.debug( "HttpConnectionManager.getConnection: config = " + hostConfiguration + ", timeout = " + timeout );
}
final HttpConnection conn = doGetConnection( hostConfiguration, timeout );
conn.getParams().setParameter( SoapUIHostConfiguration.SOAPUI_SSL_CONFIG,
hostConfiguration.getParams().getParameter( SoapUIHostConfiguration.SOAPUI_SSL_CONFIG ) );
// wrap the connection in an adapter so we can ensure it is used
// only once
return new HttpConnectionAdapter( conn );
}
/**
* @see HttpConnectionManager#getConnection(HostConfiguration, long)
*
* @deprecated Use #getConnectionWithTimeout(HostConfiguration, long)
*/
public HttpConnection getConnection( HostConfiguration hostConfiguration, long timeout ) throws HttpException
{
LOG.trace( "enter HttpConnectionManager.getConnection(HostConfiguration, long)" );
try
{
return getConnectionWithTimeout( hostConfiguration, timeout );
}
catch( ConnectionPoolTimeoutException e )
{
throw new HttpException( e.getMessage() );
}
}
private HttpConnection doGetConnection( HostConfiguration hostConfiguration, long timeout )
throws ConnectionPoolTimeoutException
{
HttpConnection connection = null;
int maxHostConnections = this.params.getMaxConnectionsPerHost( hostConfiguration );
int maxTotalConnections = this.params.getMaxTotalConnections();
synchronized( connectionPool )
{
// we clone the hostConfiguration
// so that it cannot be changed once the connection has been retrieved
hostConfiguration = new SoapUIHostConfiguration( hostConfiguration );
HostConnectionPool hostPool = connectionPool.getHostPool( hostConfiguration, true );
WaitingThread waitingThread = null;
boolean useTimeout = ( timeout > 0 );
long timeToWait = timeout;
long startWait = 0;
long endWait = 0;
while( connection == null )
{
if( shutdown )
{
throw new IllegalStateException( "Connection factory has been shutdown." );
}
// happen to have a free connection with the right specs
//
if( hostPool.freeConnections.size() > 0 )
{
connection = connectionPool.getFreeConnection( hostConfiguration );
// have room to make more
//
}
else if( ( hostPool.numConnections < maxHostConnections )
&& ( connectionPool.numConnections < maxTotalConnections ) )
{
connection = connectionPool.createConnection( hostConfiguration );
// have room to add host connection, and there is at least one
// free
// connection that can be liberated to make overall room
//
}
else if( ( hostPool.numConnections < maxHostConnections ) && ( connectionPool.freeConnections.size() > 0 ) )
{
connectionPool.deleteLeastUsedConnection();
connection = connectionPool.createConnection( hostConfiguration );
// otherwise, we have to wait for one of the above conditions to
// become true
//
}
else
{
// TODO: keep track of which hostConfigurations have waiting
// threads, so they avoid being sacrificed before necessary
try
{
if( useTimeout && timeToWait <= 0 )
{
throw new ConnectionPoolTimeoutException( "Timeout waiting for connection" );
}
if( LOG.isDebugEnabled() )
{
LOG.debug( "Unable to get a connection, waiting..., hostConfig=" + hostConfiguration );
}
if( waitingThread == null )
{
waitingThread = new WaitingThread();
waitingThread.hostConnectionPool = hostPool;
waitingThread.thread = Thread.currentThread();
}
else
{
waitingThread.interruptedByConnectionPool = false;
}
if( useTimeout )
{
startWait = System.currentTimeMillis();
}
hostPool.waitingThreads.addLast( waitingThread );
connectionPool.waitingThreads.addLast( waitingThread );
connectionPool.wait( timeToWait );
}
catch( InterruptedException e )
{
if( !waitingThread.interruptedByConnectionPool )
{
LOG.debug( "Interrupted while waiting for connection", e );
throw new IllegalThreadStateException(
"Interrupted while waiting in MultiThreadedHttpConnectionManager" );
}
// Else, do nothing, we were interrupted by the connection
// pool
// and should now have a connection waiting for us, continue
// in the loop and let's get it.
}
finally
{
if( !waitingThread.interruptedByConnectionPool )
{
// Either we timed out, experienced a "spurious wakeup", or
// were
// interrupted by an external thread. Regardless we need to
// cleanup for ourselves in the wait queue.
hostPool.waitingThreads.remove( waitingThread );
connectionPool.waitingThreads.remove( waitingThread );
}
if( useTimeout )
{
endWait = System.currentTimeMillis();
timeToWait -= ( endWait - startWait );
}
}
}
}
}
return connection;
}
/**
* Gets the total number of pooled connections for the given host
* configuration. This is the total number of connections that have been
* created and are still in use by this connection manager for the host
* configuration. This value will not exceed the
* {@link #getMaxConnectionsPerHost() maximum number of connections per host}
* .
*
* @param hostConfiguration
* The host configuration
* @return The total number of pooled connections
*/
public int getConnectionsInPool( HostConfiguration hostConfiguration )
{
synchronized( connectionPool )
{
HostConnectionPool hostPool = connectionPool.getHostPool( hostConfiguration, false );
return ( hostPool != null ) ? hostPool.numConnections : 0;
}
}
/**
* Gets the total number of pooled connections. This is the total number of
* connections that have been created and are still in use by this connection
* manager. This value will not exceed the {@link #getMaxTotalConnections()
* maximum number of connections}.
*
* @return the total number of pooled connections
*/
public int getConnectionsInPool()
{
synchronized( connectionPool )
{
return connectionPool.numConnections;
}
}
/**
* Gets the number of connections in use for this configuration.
*
* @param hostConfiguration
* the key that connections are tracked on
* @return the number of connections in use
*
* @deprecated Use {@link #getConnectionsInPool(HostConfiguration)}
*/
public int getConnectionsInUse( HostConfiguration hostConfiguration )
{
return getConnectionsInPool( hostConfiguration );
}
/**
* Gets the total number of connections in use.
*
* @return the total number of connections in use
*
* @deprecated Use {@link #getConnectionsInPool()}
*/
public int getConnectionsInUse()
{
return getConnectionsInPool();
}
/**
* Deletes all closed connections. Only connections currently owned by the
* connection manager are processed.
*
* @see HttpConnection#isOpen()
*
* @since 3.0
*/
public void deleteClosedConnections()
{
connectionPool.deleteClosedConnections();
}
/**
* @since 3.0
*/
public void closeIdleConnections( long idleTimeout )
{
connectionPool.closeIdleConnections( idleTimeout );
deleteClosedConnections();
}
/**
* Make the given HttpConnection available for use by other requests. If
* another thread is blocked in getConnection() that could use this
* connection, it will be woken up.
*
* @param conn
* the HttpConnection to make available.
*/
public void releaseConnection( HttpConnection conn )
{
LOG.trace( "enter HttpConnectionManager.releaseConnection(HttpConnection)" );
if( conn instanceof HttpConnectionAdapter )
{
// connections given out are wrapped in an HttpConnectionAdapter
conn = ( ( HttpConnectionAdapter )conn ).getWrappedConnection();
}
else
{
// this is okay, when an HttpConnectionAdapter is released
// is releases the real connection
}
// make sure that the response has been read.
finishLastResponse( conn );
connectionPool.freeConnection( conn );
}
private void finishLastResponse( HttpConnection conn )
{
InputStream lastResponse = conn.getLastResponseInputStream();
if( lastResponse != null )
{
conn.setLastResponseInputStream( null );
try
{
lastResponse.close();
}
catch( IOException ioe )
{
conn.close();
}
}
}
/**
* Gets the host configuration for a connection.
*
* @param conn
* the connection to get the configuration of
* @return a new HostConfiguration
*/
private HostConfiguration configurationForConnection( HttpConnection conn )
{
HostConfiguration connectionConfiguration = new SoapUIHostConfiguration();
connectionConfiguration.setHost( conn.getHost(), conn.getPort(), conn.getProtocol() );
if( conn.getLocalAddress() != null )
{
connectionConfiguration.setLocalAddress( conn.getLocalAddress() );
}
if( conn.getProxyHost() != null )
{
connectionConfiguration.setProxy( conn.getProxyHost(), conn.getProxyPort() );
}
if( conn.getParams().getParameter( SoapUIHostConfiguration.SOAPUI_SSL_CONFIG ) != null )
connectionConfiguration.getParams().setParameter( SoapUIHostConfiguration.SOAPUI_SSL_CONFIG,
conn.getParams().getParameter( SoapUIHostConfiguration.SOAPUI_SSL_CONFIG ) );
return connectionConfiguration;
}
/**
* Returns {@link HttpConnectionManagerParams parameters} associated with
* this connection manager.
*
* @since 3.0
*
* @see HttpConnectionManagerParams
*/
public HttpConnectionManagerParams getParams()
{
return this.params;
}
/**
* Assigns {@link HttpConnectionManagerParams parameters} for this connection
* manager.
*
* @since 3.0
*
* @see HttpConnectionManagerParams
*/
public void setParams( final HttpConnectionManagerParams params )
{
if( params == null )
{
throw new IllegalArgumentException( "Parameters may not be null" );
}
this.params = params;
}
/**
* Global Connection Pool, including per-host pools
*/
private class ConnectionPool
{
/** The list of free connections */
private LinkedList freeConnections = new LinkedList();
/** The list of WaitingThreads waiting for a connection */
private LinkedList waitingThreads = new LinkedList();
/**
* Map where keys are {@link HostConfiguration}s and values are
* {@link HostConnectionPool}s
*/
private final Map mapHosts = new HashMap();
private IdleConnectionHandler idleConnectionHandler = new IdleConnectionHandler();
/** The number of created connections */
private int numConnections = 0;
/**
* Cleans up all connection pool resources.
*/
public synchronized void shutdown()
{
// close all free connections
Iterator> iter = freeConnections.iterator();
while( iter.hasNext() )
{
HttpConnection conn = ( HttpConnection )iter.next();
iter.remove();
conn.close();
}
// close all connections that have been checked out
shutdownCheckedOutConnections( this );
// interrupt all waiting threads
iter = waitingThreads.iterator();
while( iter.hasNext() )
{
WaitingThread waiter = ( WaitingThread )iter.next();
iter.remove();
waiter.interruptedByConnectionPool = true;
waiter.thread.interrupt();
}
// clear out map hosts
mapHosts.clear();
// remove all references to connections
idleConnectionHandler.removeAll();
}
/**
* Creates a new connection and returns it for use of the calling method.
*
* @param hostConfiguration
* the configuration for the connection
* @return a new connection or null
if none are available
*/
public synchronized HttpConnection createConnection( HostConfiguration hostConfiguration )
{
HostConnectionPool hostPool = getHostPool( hostConfiguration, true );
if( LOG.isDebugEnabled() )
{
LOG.debug( "Allocating new connection, hostConfig=" + hostConfiguration );
}
HttpConnectionWithReference connection = new HttpConnectionWithReference( hostConfiguration );
connection.getParams().setDefaults( SoapUIMultiThreadedHttpConnectionManager.this.params );
connection.setHttpConnectionManager( SoapUIMultiThreadedHttpConnectionManager.this );
numConnections++ ;
hostPool.numConnections++ ;
// store a reference to this connection so that it can be cleaned up
// in the event it is not correctly released
storeReferenceToConnection( connection, hostConfiguration, this );
return connection;
}
/**
* Handles cleaning up for a lost connection with the given config.
* Decrements any connection counts and notifies waiting threads, if
* appropriate.
*
* @param config
* the host configuration of the connection that was lost
*/
public synchronized void handleLostConnection( HostConfiguration config )
{
HostConnectionPool hostPool = getHostPool( config, true );
hostPool.numConnections-- ;
if( ( hostPool.numConnections == 0 ) && hostPool.waitingThreads.isEmpty() )
{
mapHosts.remove( config );
}
numConnections-- ;
notifyWaitingThread( config );
}
/**
* Get the pool (list) of connections available for the given hostConfig.
*
* @param hostConfiguration
* the configuraton for the connection pool
* @param create
* true
to create a pool if not found,
* false
to return null
*
* @return a pool (list) of connections available for the given config, or
* null
if neither found nor created
*/
public synchronized HostConnectionPool getHostPool( HostConfiguration hostConfiguration, boolean create )
{
LOG.trace( "enter HttpConnectionManager.ConnectionPool.getHostPool(HostConfiguration)" );
// Look for a list of connections for the given config
HostConnectionPool listConnections = ( HostConnectionPool )mapHosts.get( hostConfiguration );
if( ( listConnections == null ) && create )
{
// First time for this config
listConnections = new HostConnectionPool();
listConnections.hostConfiguration = hostConfiguration;
mapHosts.put( hostConfiguration, listConnections );
}
return listConnections;
}
/**
* If available, get a free connection for this host
*
* @param hostConfiguration
* the configuraton for the connection pool
* @return an available connection for the given config
*/
public synchronized HttpConnection getFreeConnection( HostConfiguration hostConfiguration )
{
HttpConnectionWithReference connection = null;
HostConnectionPool hostPool = getHostPool( hostConfiguration, false );
if( ( hostPool != null ) && ( hostPool.freeConnections.size() > 0 ) )
{
connection = ( HttpConnectionWithReference )hostPool.freeConnections.removeLast();
freeConnections.remove( connection );
// store a reference to this connection so that it can be cleaned up
// in the event it is not correctly released
storeReferenceToConnection( connection, hostConfiguration, this );
if( LOG.isDebugEnabled() )
{
LOG.debug( "Getting free connection, hostConfig=" + hostConfiguration );
}
// remove the connection from the timeout handler
idleConnectionHandler.remove( connection );
}
else if( LOG.isDebugEnabled() )
{
LOG.debug( "There were no free connections to get, hostConfig=" + hostConfiguration );
}
return connection;
}
/**
* Deletes all closed connections.
*/
public synchronized void deleteClosedConnections()
{
Iterator iter = freeConnections.iterator();
while( iter.hasNext() )
{
HttpConnection conn = ( HttpConnection )iter.next();
if( !conn.isOpen() )
{
iter.remove();
deleteConnection( conn );
}
}
}
/**
* Closes idle connections.
*
* @param idleTimeout
*/
public synchronized void closeIdleConnections( long idleTimeout )
{
idleConnectionHandler.closeIdleConnections( idleTimeout );
}
/**
* Deletes the given connection. This will remove all reference to the
* connection so that it can be GCed.
*
*
* Note: Does not remove the connection from the freeConnections
* list. It is assumed that the caller has already handled this case.
*
*
* @param connection
* The connection to delete
*/
private synchronized void deleteConnection( HttpConnection connection )
{
HostConfiguration connectionConfiguration = configurationForConnection( connection );
if( LOG.isDebugEnabled() )
{
LOG.debug( "Reclaiming connection, hostConfig=" + connectionConfiguration );
}
connection.close();
HostConnectionPool hostPool = getHostPool( connectionConfiguration, true );
hostPool.freeConnections.remove( connection );
hostPool.numConnections-- ;
numConnections-- ;
if( ( hostPool.numConnections == 0 ) && hostPool.waitingThreads.isEmpty() )
{
mapHosts.remove( connectionConfiguration );
}
// remove the connection from the timeout handler
idleConnectionHandler.remove( connection );
}
/**
* Close and delete an old, unused connection to make room for a new one.
*/
public synchronized void deleteLeastUsedConnection()
{
HttpConnection connection = ( HttpConnection )freeConnections.removeFirst();
if( connection != null )
{
deleteConnection( connection );
}
else if( LOG.isDebugEnabled() )
{
LOG.debug( "Attempted to reclaim an unused connection but there were none." );
}
}
/**
* Notifies a waiting thread that a connection for the given configuration
* is available.
*
* @param configuration
* the host config to use for notifying
* @see #notifyWaitingThread(HostConnectionPool)
*/
public synchronized void notifyWaitingThread( HostConfiguration configuration )
{
notifyWaitingThread( getHostPool( configuration, true ) );
}
/**
* Notifies a waiting thread that a connection for the given configuration
* is available. This will wake a thread waiting in this host pool or if
* there is not one a thread in the connection pool will be notified.
*
* @param hostPool
* the host pool to use for notifying
*/
public synchronized void notifyWaitingThread( HostConnectionPool hostPool )
{
// find the thread we are going to notify, we want to ensure that each
// waiting thread is only interrupted once so we will remove it from
// all wait queues before interrupting it
WaitingThread waitingThread = null;
if( hostPool.waitingThreads.size() > 0 )
{
if( LOG.isDebugEnabled() )
{
LOG.debug( "Notifying thread waiting on host pool, hostConfig=" + hostPool.hostConfiguration );
}
waitingThread = ( WaitingThread )hostPool.waitingThreads.removeFirst();
waitingThreads.remove( waitingThread );
}
else if( waitingThreads.size() > 0 )
{
if( LOG.isDebugEnabled() )
{
LOG.debug( "No-one waiting on host pool, notifying next waiting thread." );
}
waitingThread = ( WaitingThread )waitingThreads.removeFirst();
waitingThread.hostConnectionPool.waitingThreads.remove( waitingThread );
}
else if( LOG.isDebugEnabled() )
{
LOG.debug( "Notifying no-one, there are no waiting threads" );
}
if( waitingThread != null )
{
waitingThread.interruptedByConnectionPool = true;
waitingThread.thread.interrupt();
}
}
/**
* Marks the given connection as free.
*
* @param conn
* a connection that is no longer being used
*/
public void freeConnection( HttpConnection conn )
{
HostConfiguration connectionConfiguration = configurationForConnection( conn );
if( LOG.isDebugEnabled() )
{
LOG.debug( "Freeing connection, hostConfig=" + connectionConfiguration );
}
synchronized( this )
{
if( shutdown )
{
// the connection manager has been shutdown, release the
// connection's
// resources and get out of here
conn.close();
return;
}
HostConnectionPool hostPool = getHostPool( connectionConfiguration, true );
// Put the connect back in the available list and notify a waiter
hostPool.freeConnections.add( conn );
if( hostPool.numConnections == 0 )
{
// for some reason this connection pool didn't already exist
LOG.error( "Host connection pool not found, hostConfig=" + connectionConfiguration );
hostPool.numConnections = 1;
}
freeConnections.add( conn );
// we can remove the reference to this connection as we have control
// over
// it again. this also ensures that the connection manager can be
// GCed
removeReferenceToConnection( ( HttpConnectionWithReference )conn );
if( numConnections == 0 )
{
// for some reason this connection pool didn't already exist
LOG.error( "Host connection pool not found, hostConfig=" + connectionConfiguration );
numConnections = 1;
}
// register the connection with the timeout handler
idleConnectionHandler.add( conn );
notifyWaitingThread( hostPool );
}
}
}
/**
* A simple struct-like class to combine the objects needed to release a
* connection's resources when claimed by the garbage collector.
*/
private static class ConnectionSource
{
/** The connection pool that created the connection */
public ConnectionPool connectionPool;
/** The connection's host configuration */
public HostConfiguration hostConfiguration;
}
/**
* A simple struct-like class to combine the connection list and the count of
* created connections.
*/
private static class HostConnectionPool
{
/** The hostConfig this pool is for */
public HostConfiguration hostConfiguration;
/** The list of free connections */
public LinkedList freeConnections = new LinkedList();
/** The list of WaitingThreads for this host */
public LinkedList waitingThreads = new LinkedList();
/** The number of created connections */
public int numConnections = 0;
}
/**
* A simple struct-like class to combine the waiting thread and the
* connection pool it is waiting on.
*/
private static class WaitingThread
{
/** The thread that is waiting for a connection */
public Thread thread;
/** The connection pool the thread is waiting for */
public HostConnectionPool hostConnectionPool;
/**
* Flag to indicate if the thread was interrupted by the ConnectionPool.
* Set to true inside
* {@link ConnectionPool#notifyWaitingThread(HostConnectionPool)} before
* the thread is interrupted.
*/
public boolean interruptedByConnectionPool = false;
}
/**
* A thread for listening for HttpConnections reclaimed by the garbage
* collector.
*/
private static class ReferenceQueueThread extends Thread
{
private volatile boolean shutdown = false;
/**
* Create an instance and make this a daemon thread.
*/
public ReferenceQueueThread()
{
setDaemon( true );
setName( "MultiThreadedHttpConnectionManager cleanup" );
}
public void shutdown()
{
this.shutdown = true;
this.interrupt();
}
/**
* Handles cleaning up for the given connection reference.
*
* @param ref
* the reference to clean up
*/
private void handleReference( Reference ref )
{
ConnectionSource source = null;
synchronized( REFERENCE_TO_CONNECTION_SOURCE )
{
source = ( ConnectionSource )REFERENCE_TO_CONNECTION_SOURCE.remove( ref );
}
// only clean up for this reference if it is still associated with
// a ConnectionSource
if( source != null )
{
if( LOG.isDebugEnabled() )
{
LOG.debug( "Connection reclaimed by garbage collector, hostConfig=" + source.hostConfiguration );
}
source.connectionPool.handleLostConnection( source.hostConfiguration );
}
}
/**
* Start execution.
*/
public void run()
{
while( !shutdown )
{
try
{
// remove the next reference and process it
Reference ref = REFERENCE_QUEUE.remove();
if( ref != null )
{
handleReference( ref );
}
}
catch( InterruptedException e )
{
LOG.debug( "ReferenceQueueThread interrupted", e );
}
}
}
}
/**
* A connection that keeps a reference to itself.
*/
public static class HttpConnectionWithReference extends HttpConnection implements ConnectionWithSocket
{
public WeakReference reference = new WeakReference( this, REFERENCE_QUEUE );
/**
* @param hostConfiguration
*/
public HttpConnectionWithReference( HostConfiguration hostConfiguration )
{
super( hostConfiguration );
}
public Socket getConnectionSocket()
{
return getSocket();
}
}
/**
* An HttpConnection wrapper that ensures a connection cannot be used once
* released.
*/
public static class HttpConnectionAdapter extends HttpConnection implements ConnectionWithSocket
{
// the wrapped connection
private HttpConnection wrappedConnection;
/**
* Creates a new HttpConnectionAdapter.
*
* @param connection
* the connection to be wrapped
*/
public HttpConnectionAdapter( HttpConnection connection )
{
super( connection.getHost(), connection.getPort(), connection.getProtocol() );
this.wrappedConnection = connection;
}
/**
* Tests if the wrapped connection is still available.
*
* @return boolean
*/
protected boolean hasConnection()
{
return wrappedConnection != null;
}
/**
* @return HttpConnection
*/
HttpConnection getWrappedConnection()
{
return wrappedConnection;
}
public void close()
{
if( hasConnection() )
{
wrappedConnection.close();
}
else
{
// do nothing
}
}
public InetAddress getLocalAddress()
{
if( hasConnection() )
{
return wrappedConnection.getLocalAddress();
}
else
{
return null;
}
}
/**
* @deprecated
*/
public boolean isStaleCheckingEnabled()
{
if( hasConnection() )
{
return wrappedConnection.isStaleCheckingEnabled();
}
else
{
return false;
}
}
public void setLocalAddress( InetAddress localAddress )
{
if( hasConnection() )
{
wrappedConnection.setLocalAddress( localAddress );
}
else
{
throw new IllegalStateException( "Connection has been released" );
}
}
/**
* @deprecated
*/
public void setStaleCheckingEnabled( boolean staleCheckEnabled )
{
if( hasConnection() )
{
wrappedConnection.setStaleCheckingEnabled( staleCheckEnabled );
}
else
{
throw new IllegalStateException( "Connection has been released" );
}
}
public String getHost()
{
if( hasConnection() )
{
return wrappedConnection.getHost();
}
else
{
return null;
}
}
public HttpConnectionManager getHttpConnectionManager()
{
if( hasConnection() )
{
return wrappedConnection.getHttpConnectionManager();
}
else
{
return null;
}
}
public InputStream getLastResponseInputStream()
{
if( hasConnection() )
{
return wrappedConnection.getLastResponseInputStream();
}
else
{
return null;
}
}
public int getPort()
{
if( hasConnection() )
{
return wrappedConnection.getPort();
}
else
{
return -1;
}
}
public Protocol getProtocol()
{
if( hasConnection() )
{
return wrappedConnection.getProtocol();
}
else
{
return null;
}
}
public String getProxyHost()
{
if( hasConnection() )
{
return wrappedConnection.getProxyHost();
}
else
{
return null;
}
}
public int getProxyPort()
{
if( hasConnection() )
{
return wrappedConnection.getProxyPort();
}
else
{
return -1;
}
}
public OutputStream getRequestOutputStream() throws IOException, IllegalStateException
{
if( hasConnection() )
{
return wrappedConnection.getRequestOutputStream();
}
else
{
return null;
}
}
public InputStream getResponseInputStream() throws IOException, IllegalStateException
{
if( hasConnection() )
{
return wrappedConnection.getResponseInputStream();
}
else
{
return null;
}
}
public boolean isOpen()
{
if( hasConnection() )
{
return wrappedConnection.isOpen();
}
else
{
return false;
}
}
public boolean closeIfStale() throws IOException
{
if( hasConnection() )
{
return wrappedConnection.closeIfStale();
}
else
{
return false;
}
}
public boolean isProxied()
{
if( hasConnection() )
{
return wrappedConnection.isProxied();
}
else
{
return false;
}
}
public boolean isResponseAvailable() throws IOException
{
if( hasConnection() )
{
return wrappedConnection.isResponseAvailable();
}
else
{
return false;
}
}
public boolean isResponseAvailable( int timeout ) throws IOException
{
if( hasConnection() )
{
return wrappedConnection.isResponseAvailable( timeout );
}
else
{
return false;
}
}
public boolean isSecure()
{
if( hasConnection() )
{
return wrappedConnection.isSecure();
}
else
{
return false;
}
}
public boolean isTransparent()
{
if( hasConnection() )
{
return wrappedConnection.isTransparent();
}
else
{
return false;
}
}
public void open() throws IOException
{
if( hasConnection() )
{
wrappedConnection.open();
}
else
{
throw new IllegalStateException( "Connection has been released" );
}
}
/**
* @deprecated
*/
public void print( String data ) throws IOException, IllegalStateException
{
if( hasConnection() )
{
wrappedConnection.print( data );
}
else
{
throw new IllegalStateException( "Connection has been released" );
}
}
public void printLine() throws IOException, IllegalStateException
{
if( hasConnection() )
{
wrappedConnection.printLine();
}
else
{
throw new IllegalStateException( "Connection has been released" );
}
}
/**
* @deprecated
*/
public void printLine( String data ) throws IOException, IllegalStateException
{
if( hasConnection() )
{
wrappedConnection.printLine( data );
}
else
{
throw new IllegalStateException( "Connection has been released" );
}
}
/**
* @deprecated
*/
public String readLine() throws IOException, IllegalStateException
{
if( hasConnection() )
{
return wrappedConnection.readLine();
}
else
{
throw new IllegalStateException( "Connection has been released" );
}
}
public String readLine( String charset ) throws IOException, IllegalStateException
{
if( hasConnection() )
{
return wrappedConnection.readLine( charset );
}
else
{
throw new IllegalStateException( "Connection has been released" );
}
}
public void releaseConnection()
{
if( !isLocked() && hasConnection() )
{
HttpConnection wrappedConnection = this.wrappedConnection;
this.wrappedConnection = null;
wrappedConnection.releaseConnection();
}
else
{
// do nothing
}
}
/**
* @deprecated
*/
public void setConnectionTimeout( int timeout )
{
if( hasConnection() )
{
wrappedConnection.setConnectionTimeout( timeout );
}
else
{
// do nothing
}
}
public void setHost( String host ) throws IllegalStateException
{
if( hasConnection() )
{
wrappedConnection.setHost( host );
}
else
{
// do nothing
}
}
public void setHttpConnectionManager( HttpConnectionManager httpConnectionManager )
{
if( hasConnection() )
{
wrappedConnection.setHttpConnectionManager( httpConnectionManager );
}
else
{
// do nothing
}
}
public void setLastResponseInputStream( InputStream inStream )
{
if( hasConnection() )
{
wrappedConnection.setLastResponseInputStream( inStream );
}
else
{
// do nothing
}
}
public void setPort( int port ) throws IllegalStateException
{
if( hasConnection() )
{
wrappedConnection.setPort( port );
}
else
{
// do nothing
}
}
public void setProtocol( Protocol protocol )
{
if( hasConnection() )
{
wrappedConnection.setProtocol( protocol );
}
else
{
// do nothing
}
}
public void setProxyHost( String host ) throws IllegalStateException
{
if( hasConnection() )
{
wrappedConnection.setProxyHost( host );
}
else
{
// do nothing
}
}
public void setProxyPort( int port ) throws IllegalStateException
{
if( hasConnection() )
{
wrappedConnection.setProxyPort( port );
}
else
{
// do nothing
}
}
/**
* @deprecated
*/
public void setSoTimeout( int timeout ) throws SocketException, IllegalStateException
{
if( hasConnection() )
{
wrappedConnection.setSoTimeout( timeout );
}
else
{
// do nothing
}
}
/**
* @deprecated
*/
public void shutdownOutput()
{
if( hasConnection() )
{
wrappedConnection.shutdownOutput();
}
else
{
// do nothing
}
}
public void tunnelCreated() throws IllegalStateException, IOException
{
if( hasConnection() )
{
wrappedConnection.tunnelCreated();
}
else
{
// do nothing
}
}
public void write( byte[] data, int offset, int length ) throws IOException, IllegalStateException
{
if( hasConnection() )
{
wrappedConnection.write( data, offset, length );
}
else
{
throw new IllegalStateException( "Connection has been released" );
}
}
public void write( byte[] data ) throws IOException, IllegalStateException
{
if( hasConnection() )
{
wrappedConnection.write( data );
}
else
{
throw new IllegalStateException( "Connection has been released" );
}
}
public void writeLine() throws IOException, IllegalStateException
{
if( hasConnection() )
{
wrappedConnection.writeLine();
}
else
{
throw new IllegalStateException( "Connection has been released" );
}
}
public void writeLine( byte[] data ) throws IOException, IllegalStateException
{
if( hasConnection() )
{
wrappedConnection.writeLine( data );
}
else
{
throw new IllegalStateException( "Connection has been released" );
}
}
public void flushRequestOutputStream() throws IOException
{
if( hasConnection() )
{
wrappedConnection.flushRequestOutputStream();
}
else
{
throw new IllegalStateException( "Connection has been released" );
}
}
/**
* @deprecated
*/
public int getSoTimeout() throws SocketException
{
if( hasConnection() )
{
return wrappedConnection.getSoTimeout();
}
else
{
throw new IllegalStateException( "Connection has been released" );
}
}
/**
* @deprecated
*/
public String getVirtualHost()
{
if( hasConnection() )
{
return wrappedConnection.getVirtualHost();
}
else
{
throw new IllegalStateException( "Connection has been released" );
}
}
/**
* @deprecated
*/
public void setVirtualHost( String host ) throws IllegalStateException
{
if( hasConnection() )
{
wrappedConnection.setVirtualHost( host );
}
else
{
throw new IllegalStateException( "Connection has been released" );
}
}
public int getSendBufferSize() throws SocketException
{
if( hasConnection() )
{
return wrappedConnection.getSendBufferSize();
}
else
{
throw new IllegalStateException( "Connection has been released" );
}
}
/**
* @deprecated
*/
public void setSendBufferSize( int sendBufferSize ) throws SocketException
{
if( hasConnection() )
{
wrappedConnection.setSendBufferSize( sendBufferSize );
}
else
{
throw new IllegalStateException( "Connection has been released" );
}
}
public HttpConnectionParams getParams()
{
if( hasConnection() )
{
return wrappedConnection.getParams();
}
else
{
throw new IllegalStateException( "Connection has been released" );
}
}
public void setParams( final HttpConnectionParams params )
{
if( hasConnection() )
{
wrappedConnection.setParams( params );
}
else
{
throw new IllegalStateException( "Connection has been released" );
}
}
/*
* (non-Javadoc)
*
* @see
* org.apache.commons.httpclient.HttpConnection#print(java.lang.String,
* java.lang.String)
*/
public void print( String data, String charset ) throws IOException, IllegalStateException
{
if( hasConnection() )
{
wrappedConnection.print( data, charset );
}
else
{
throw new IllegalStateException( "Connection has been released" );
}
}
/*
* (non-Javadoc)
*
* @see
* org.apache.commons.httpclient.HttpConnection#printLine(java.lang.String
* , java.lang.String)
*/
public void printLine( String data, String charset ) throws IOException, IllegalStateException
{
if( hasConnection() )
{
wrappedConnection.printLine( data, charset );
}
else
{
throw new IllegalStateException( "Connection has been released" );
}
}
/*
* (non-Javadoc)
*
* @see org.apache.commons.httpclient.HttpConnection#setSocketTimeout(int)
*/
public void setSocketTimeout( int timeout ) throws SocketException, IllegalStateException
{
if( hasConnection() )
{
wrappedConnection.setSocketTimeout( timeout );
}
else
{
throw new IllegalStateException( "Connection has been released" );
}
}
public Socket getConnectionSocket()
{
if( wrappedConnection instanceof ConnectionWithSocket )
return ( ( ConnectionWithSocket )wrappedConnection ).getConnectionSocket();
return null;
}
}
}