View Javadoc

1   /*
2    * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/MultiThreadedHttpConnectionManager.java,v 1.43 2004/09/13 16:25:19 olegk Exp $
3    * $Revision: 1.43 $
4    * $Date: 2004/09/13 16:25:19 $
5    *
6    * ====================================================================
7    *
8    *  Copyright 2002-2004 The Apache Software Foundation
9    *
10   *  Licensed under the Apache License, Version 2.0 (the "License");
11   *  you may not use this file except in compliance with the License.
12   *  You may obtain a copy of the License at
13   *
14   *      http://www.apache.org/licenses/LICENSE-2.0
15   *
16   *  Unless required by applicable law or agreed to in writing, software
17   *  distributed under the License is distributed on an "AS IS" BASIS,
18   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19   *  See the License for the specific language governing permissions and
20   *  limitations under the License.
21   * ====================================================================
22   *
23   * This software consists of voluntary contributions made by many
24   * individuals on behalf of the Apache Software Foundation.  For more
25   * information on the Apache Software Foundation, please see
26   * <http://www.apache.org/>.
27   *
28   */
29  
30  package org.apache.commons.httpclient;
31  
32  import java.io.IOException;
33  import java.io.InputStream;
34  import java.io.OutputStream;
35  import java.lang.ref.Reference;
36  import java.lang.ref.ReferenceQueue;
37  import java.lang.ref.WeakReference;
38  import java.net.InetAddress;
39  import java.net.SocketException;
40  import java.util.ArrayList;
41  import java.util.HashMap;
42  import java.util.Iterator;
43  import java.util.LinkedList;
44  import java.util.Map;
45  import java.util.WeakHashMap;
46  
47  import org.apache.commons.httpclient.params.HttpConnectionManagerParams;
48  import org.apache.commons.httpclient.params.HttpConnectionParams;
49  import org.apache.commons.httpclient.protocol.Protocol;
50  import org.apache.commons.httpclient.util.IdleConnectionHandler;
51  import org.apache.commons.logging.Log;
52  import org.apache.commons.logging.LogFactory;
53  
54  /***
55   * Manages a set of HttpConnections for various HostConfigurations.
56   *
57   * @author <a href="mailto:becke@u.washington.edu">Michael Becke</a>
58   * @author Eric Johnson
59   * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
60   * @author Carl A. Dunham
61   *
62   * @since 2.0
63   */
64  public class MultiThreadedHttpConnectionManager implements HttpConnectionManager {
65  
66      // -------------------------------------------------------- Class Variables
67      /*** Log object for this class. */
68      private static final Log LOG = LogFactory.getLog(MultiThreadedHttpConnectionManager.class);
69  
70      /*** The default maximum number of connections allowed per host */
71      public static final int DEFAULT_MAX_HOST_CONNECTIONS = 2;   // Per RFC 2616 sec 8.1.4
72  
73      /*** The default maximum number of connections allowed overall */
74      public static final int DEFAULT_MAX_TOTAL_CONNECTIONS = 20;
75  
76      /***
77       * A mapping from Reference to ConnectionSource.  Used to reclaim resources when connections
78       * are lost to the garbage collector.
79       */
80      private static final Map REFERENCE_TO_CONNECTION_SOURCE = new HashMap();
81      
82      /***
83       * The reference queue used to track when HttpConnections are lost to the
84       * garbage collector
85       */
86      private static final ReferenceQueue REFERENCE_QUEUE = new ReferenceQueue();    
87  
88      /***
89       * The thread responsible for handling lost connections.
90       */
91      private static ReferenceQueueThread REFERENCE_QUEUE_THREAD;
92      
93      /***
94       * Holds references to all active instances of this class.
95       */    
96      private static WeakHashMap ALL_CONNECTION_MANAGERS = new WeakHashMap();
97      
98      /***
99       * Shuts down and cleans up resources used by all instances of 
100      * MultiThreadedHttpConnectionManager. All static resources are released, all threads are 
101      * stopped, and {@link #shutdown()} is called on all live instaces of 
102      * MultiThreadedHttpConnectionManager.
103      *
104      * @see #shutdown()
105      */
106     public static void shutdownAll() {
107 
108         synchronized (REFERENCE_TO_CONNECTION_SOURCE) {
109             // shutdown all connection managers
110             synchronized (ALL_CONNECTION_MANAGERS) {
111                 Iterator connIter = ALL_CONNECTION_MANAGERS.keySet().iterator();
112                 while (connIter.hasNext()) {
113                     MultiThreadedHttpConnectionManager connManager = 
114                         (MultiThreadedHttpConnectionManager) connIter.next();
115                     connIter.remove();
116                     connManager.shutdown();
117                 }
118             }
119             
120             // shutdown static resources
121             if (REFERENCE_QUEUE_THREAD != null) {
122                 REFERENCE_QUEUE_THREAD.shutdown();
123                 REFERENCE_QUEUE_THREAD = null;
124             }
125             REFERENCE_TO_CONNECTION_SOURCE.clear();
126         }        
127     }    
128     
129     /***
130      * Stores the reference to the given connection along with the hostConfig and connection pool.  
131      * These values will be used to reclaim resources if the connection is lost to the garbage 
132      * collector.  This method should be called before a connection is released from the connection 
133      * manager.
134      * 
135      * <p>A static reference to the connection manager will also be stored.  To ensure that
136      * the connection manager can be GCed {@link #removeReferenceToConnection(HttpConnection)}
137      * should be called for all connections that the connection manager is storing a reference
138      * to.</p>
139      * 
140      * @param connection the connection to create a reference for
141      * @param hostConfiguration the connection's host config
142      * @param connectionPool the connection pool that created the connection
143      * 
144      * @see #removeReferenceToConnection(HttpConnection)
145      */
146     private static void storeReferenceToConnection(
147         HttpConnectionWithReference connection,
148         HostConfiguration hostConfiguration,
149         ConnectionPool connectionPool
150     ) {
151         
152         ConnectionSource source = new ConnectionSource();
153         source.connectionPool = connectionPool;
154         source.hostConfiguration = hostConfiguration;
155         
156         synchronized (REFERENCE_TO_CONNECTION_SOURCE) {
157             
158             // start the reference queue thread if needed
159             if (REFERENCE_QUEUE_THREAD == null) {
160                 REFERENCE_QUEUE_THREAD = new ReferenceQueueThread();
161                 REFERENCE_QUEUE_THREAD.start();
162             }
163             
164             REFERENCE_TO_CONNECTION_SOURCE.put(
165                 connection.reference,
166                 source
167             );
168         }
169     }
170     
171     /***
172      * Closes and releases all connections currently checked out of the given connection pool.
173      * @param connectionPool the connection pool to shutdown the connections for
174      */
175     private static void shutdownCheckedOutConnections(ConnectionPool connectionPool) {
176 
177         // keep a list of the connections to be closed
178         ArrayList connectionsToClose = new ArrayList(); 
179         
180         synchronized (REFERENCE_TO_CONNECTION_SOURCE) {
181             
182             Iterator referenceIter = REFERENCE_TO_CONNECTION_SOURCE.keySet().iterator();
183             while (referenceIter.hasNext()) {
184                 Reference ref = (Reference) referenceIter.next();
185                 ConnectionSource source = 
186                     (ConnectionSource) REFERENCE_TO_CONNECTION_SOURCE.get(ref);
187                 if (source.connectionPool == connectionPool) {
188                     referenceIter.remove();
189                     HttpConnection connection = (HttpConnection) ref.get();
190                     if (connection != null) {
191                         connectionsToClose.add(connection);
192                     }
193                 }
194             }
195         }
196 
197         // close and release the connections outside of the synchronized block to
198         // avoid holding the lock for too long
199         for (Iterator i = connectionsToClose.iterator(); i.hasNext();) {
200             HttpConnection connection = (HttpConnection) i.next();
201             connection.close();
202             // remove the reference to the connection manager. this ensures
203             // that the we don't accidentally end up here again
204             connection.setHttpConnectionManager(null);
205             connection.releaseConnection();
206         }
207     }
208     
209     /***
210      * Removes the reference being stored for the given connection.  This method should be called
211      * when the connection manager again has a direct reference to the connection.
212      * 
213      * @param connection the connection to remove the reference for
214      * 
215      * @see #storeReferenceToConnection(HttpConnection, HostConfiguration, ConnectionPool)
216      */
217     private static void removeReferenceToConnection(HttpConnectionWithReference connection) {
218         
219         synchronized (REFERENCE_TO_CONNECTION_SOURCE) {
220             REFERENCE_TO_CONNECTION_SOURCE.remove(connection.reference);
221         }
222     }    
223     
224     // ----------------------------------------------------- Instance Variables
225     /***
226      * Collection of parameters associated with this connection manager.
227      */
228     private HttpConnectionManagerParams params = new HttpConnectionManagerParams(); 
229 
230     /*** Connection Pool */
231     private ConnectionPool connectionPool;
232 
233     private boolean shutdown = false;
234     
235     /***
236      * No-args constructor
237      */
238     public MultiThreadedHttpConnectionManager() {
239         this.connectionPool = new ConnectionPool();
240         synchronized(ALL_CONNECTION_MANAGERS) {
241             ALL_CONNECTION_MANAGERS.put(this, null);
242         }
243     }
244 
245     /***
246      * Shuts down the connection manager and releases all resources.  All connections associated 
247      * with this class will be closed and released. 
248      * 
249      * <p>The connection manager can no longer be used once shutdown.  
250      * 
251      * <p>Calling this method more than once will have no effect.
252      */
253     public synchronized void shutdown() {
254         synchronized (connectionPool) {
255             if (!shutdown) {
256                 shutdown = true;
257                 connectionPool.shutdown();
258             }
259         }
260     }
261     
262     /***
263      * Gets the staleCheckingEnabled value to be set on HttpConnections that are created.
264      * 
265      * @return <code>true</code> if stale checking will be enabled on HttpConections
266      * 
267      * @see HttpConnection#isStaleCheckingEnabled()
268      * 
269      * @deprecated Use {@link HttpConnectionManagerParams#isStaleCheckingEnabled()},
270      * {@link HttpConnectionManager#getParams()}.
271      */
272     public boolean isConnectionStaleCheckingEnabled() {
273         return this.params.isStaleCheckingEnabled();
274     }
275 
276     /***
277      * Sets the staleCheckingEnabled value to be set on HttpConnections that are created.
278      * 
279      * @param connectionStaleCheckingEnabled <code>true</code> if stale checking will be enabled 
280      * on HttpConections
281      * 
282      * @see HttpConnection#setStaleCheckingEnabled(boolean)
283      * 
284      * @deprecated Use {@link HttpConnectionManagerParams#setStaleCheckingEnabled(boolean)},
285      * {@link HttpConnectionManager#getParams()}.
286      */
287     public void setConnectionStaleCheckingEnabled(boolean connectionStaleCheckingEnabled) {
288         this.params.setStaleCheckingEnabled(connectionStaleCheckingEnabled);
289     }
290 
291     /***
292      * Sets the maximum number of connections allowed for a given
293      * HostConfiguration. Per RFC 2616 section 8.1.4, this value defaults to 2.
294      *
295      * @param maxHostConnections the number of connections allowed for each
296      * hostConfiguration
297      * 
298      * @deprecated Use {@link HttpConnectionManagerParams#setDefaultMaxConnectionsPerHost(int)},
299      * {@link HttpConnectionManager#getParams()}.
300      */
301     public void setMaxConnectionsPerHost(int maxHostConnections) {
302         this.params.setDefaultMaxConnectionsPerHost(maxHostConnections);
303     }
304 
305     /***
306      * Gets the maximum number of connections allowed for a given
307      * hostConfiguration.
308      *
309      * @return The maximum number of connections allowed for a given
310      * hostConfiguration.
311      * 
312      * @deprecated Use {@link HttpConnectionManagerParams#getDefaultMaxConnectionsPerHost()},
313      * {@link HttpConnectionManager#getParams()}.
314      */
315     public int getMaxConnectionsPerHost() {
316         return this.params.getDefaultMaxConnectionsPerHost();
317     }
318 
319     /***
320      * Sets the maximum number of connections allowed in the system.
321      *
322      * @param maxTotalConnections the maximum number of connections allowed
323      * 
324      * @deprecated Use {@link HttpConnectionManagerParams#setMaxTotalConnections(int)},
325      * {@link HttpConnectionManager#getParams()}.
326      */
327     public void setMaxTotalConnections(int maxTotalConnections) {
328         this.params.getMaxTotalConnections();
329     }
330 
331     /***
332      * Gets the maximum number of connections allowed in the system.
333      *
334      * @return The maximum number of connections allowed
335      * 
336      * @deprecated Use {@link HttpConnectionManagerParams#getMaxTotalConnections()},
337      * {@link HttpConnectionManager#getParams()}.
338      */
339     public int getMaxTotalConnections() {
340         return this.params.getMaxTotalConnections();
341     }
342 
343     /***
344      * @see HttpConnectionManager#getConnection(HostConfiguration)
345      */
346     public HttpConnection getConnection(HostConfiguration hostConfiguration) {
347 
348         while (true) {
349             try {
350                 return getConnectionWithTimeout(hostConfiguration, 0);
351             } catch (ConnectionPoolTimeoutException e) {
352                 // we'll go ahead and log this, but it should never happen. HttpExceptions
353                 // are only thrown when the timeout occurs and since we have no timeout
354                 // it should never happen.
355                 LOG.debug(
356                     "Unexpected exception while waiting for connection",
357                     e
358                 );
359             }
360         }
361     }
362 
363     /***
364      * @see HttpConnectionManager#getConnectionWithTimeout(HostConfiguration, long)
365      * 
366      * @since 3.0
367      */
368     public HttpConnection getConnectionWithTimeout(HostConfiguration hostConfiguration, 
369         long timeout) throws ConnectionPoolTimeoutException {
370 
371         LOG.trace("enter HttpConnectionManager.getConnectionWithTimeout(HostConfiguration, long)");
372 
373         if (hostConfiguration == null) {
374             throw new IllegalArgumentException("hostConfiguration is null");
375         }
376 
377         if (LOG.isDebugEnabled()) {
378             LOG.debug("HttpConnectionManager.getConnection:  config = "
379                 + hostConfiguration + ", timeout = " + timeout);
380         }
381 
382         final HttpConnection conn = doGetConnection(hostConfiguration, timeout);
383 
384         // wrap the connection in an adapter so we can ensure it is used 
385         // only once
386         return new HttpConnectionAdapter(conn);
387     }
388 
389 	/***
390 	 * @see HttpConnectionManager#getConnection(HostConfiguration, long)
391 	 * 
392 	 * @deprecated Use #getConnectionWithTimeout(HostConfiguration, long)
393 	 */
394 	public HttpConnection getConnection(HostConfiguration hostConfiguration, 
395 		long timeout) throws HttpException {
396 
397 		LOG.trace("enter HttpConnectionManager.getConnection(HostConfiguration, long)");
398 		try {
399 			return getConnectionWithTimeout(hostConfiguration, timeout);
400 		} catch(ConnectionPoolTimeoutException e) {
401 			throw new HttpException(e.getMessage());
402 		}
403 	}
404 
405     /***
406      * Gets a connection or waits if one is not available.  A connection is
407      * available if one exists that is not being used or if fewer than
408      * maxHostConnections have been created in the connectionPool, and fewer
409      * than maxTotalConnections have been created in all connectionPools.
410      *
411      * @param hostConfiguration The host configuration.
412      * @param timeout the number of milliseconds to wait for a connection, 0 to
413      * wait indefinitely
414      *
415      * @return HttpConnection an available connection
416      *
417      * @throws HttpException if a connection does not become available in
418      * 'timeout' milliseconds
419      */
420     private HttpConnection doGetConnection(HostConfiguration hostConfiguration, 
421         long timeout) throws ConnectionPoolTimeoutException {
422 
423         HttpConnection connection = null;
424 
425         int maxHostConnections = this.params.getMaxConnectionsPerHost(hostConfiguration);
426         int maxTotalConnections = this.params.getMaxTotalConnections();
427         
428         synchronized (connectionPool) {
429 
430             // we clone the hostConfiguration
431             // so that it cannot be changed once the connection has been retrieved
432             hostConfiguration = new HostConfiguration(hostConfiguration);
433             HostConnectionPool hostPool = connectionPool.getHostPool(hostConfiguration);
434             WaitingThread waitingThread = null;
435 
436             boolean useTimeout = (timeout > 0);
437             long timeToWait = timeout;
438             long startWait = 0;
439             long endWait = 0;
440 
441             while (connection == null) {
442 
443                 if (shutdown) {
444                     throw new IllegalStateException("Connection factory has been shutdown.");
445                 }
446                 
447                 // happen to have a free connection with the right specs
448                 //
449                 if (hostPool.freeConnections.size() > 0) {
450                     connection = connectionPool.getFreeConnection(hostConfiguration);
451 
452                 // have room to make more
453                 //
454                 } else if ((hostPool.numConnections < maxHostConnections) 
455                     && (connectionPool.numConnections < maxTotalConnections)) {
456 
457                     connection = connectionPool.createConnection(hostConfiguration);
458 
459                 // have room to add host connection, and there is at least one free
460                 // connection that can be liberated to make overall room
461                 //
462                 } else if ((hostPool.numConnections < maxHostConnections) 
463                     && (connectionPool.freeConnections.size() > 0)) {
464 
465                     connectionPool.deleteLeastUsedConnection();
466                     connection = connectionPool.createConnection(hostConfiguration);
467 
468                 // otherwise, we have to wait for one of the above conditions to
469                 // become true
470                 //
471                 } else {
472                     // TODO: keep track of which hostConfigurations have waiting
473                     // threads, so they avoid being sacrificed before necessary
474 
475                     try {
476                         
477                         if (useTimeout && timeToWait <= 0) {
478                             throw new ConnectionPoolTimeoutException("Timeout waiting for connection");
479                         }
480                         
481                         if (LOG.isDebugEnabled()) {
482                             LOG.debug("Unable to get a connection, waiting..., hostConfig=" + hostConfiguration);
483                         }
484                         
485                         if (waitingThread == null) {
486                             waitingThread = new WaitingThread();
487                             waitingThread.hostConnectionPool = hostPool;
488                             waitingThread.thread = Thread.currentThread();
489                         }
490                                     
491                         if (useTimeout) {
492                             startWait = System.currentTimeMillis();
493                         }
494                         
495                         hostPool.waitingThreads.addLast(waitingThread);
496                         connectionPool.waitingThreads.addLast(waitingThread);
497                         connectionPool.wait(timeToWait);
498                         
499                         // we have not been interrupted so we need to remove ourselves from the 
500                         // wait queue
501                         hostPool.waitingThreads.remove(waitingThread);
502                         connectionPool.waitingThreads.remove(waitingThread);
503                     } catch (InterruptedException e) {
504                         // do nothing
505                     } finally {
506                         if (useTimeout) {
507                             endWait = System.currentTimeMillis();
508                             timeToWait -= (endWait - startWait);
509                         }
510                     }
511                 }
512             }
513         }
514         return connection;
515     }
516 
517     /***
518      * Gets the total number of pooled connections for the given host configuration.  This 
519      * is the total number of connections that have been created and are still in use 
520      * by this connection manager for the host configuration.  This value will
521      * not exceed the {@link #getMaxConnectionsPerHost() maximum number of connections per
522      * host}.
523      * 
524      * @param hostConfiguration The host configuration
525      * @return The total number of pooled connections
526      */
527     public int getConnectionsInPool(HostConfiguration hostConfiguration) {
528         synchronized (connectionPool) {
529             HostConnectionPool hostPool = connectionPool.getHostPool(hostConfiguration);
530             return hostPool.numConnections;
531         }
532     }
533 
534     /***
535      * Gets the total number of pooled connections.  This is the total number of 
536      * connections that have been created and are still in use by this connection 
537      * manager.  This value will not exceed the {@link #getMaxTotalConnections() 
538      * maximum number of connections}.
539      * 
540      * @return the total number of pooled connections
541      */
542     public int getConnectionsInPool() {
543         synchronized (connectionPool) {
544             return connectionPool.numConnections;
545         }
546     }
547     
548     /***
549      * Gets the number of connections in use for this configuration.
550      *
551      * @param hostConfiguration the key that connections are tracked on
552      * @return the number of connections in use
553      * 
554      * @deprecated Use {@link #getConnectionsInPool(HostConfiguration)}
555      */
556     public int getConnectionsInUse(HostConfiguration hostConfiguration) {
557         return getConnectionsInPool(hostConfiguration);
558     }
559 
560     /***
561      * Gets the total number of connections in use.
562      * 
563      * @return the total number of connections in use
564      * 
565      * @deprecated Use {@link #getConnectionsInPool()}
566      */
567     public int getConnectionsInUse() {
568         return getConnectionsInPool();
569     }
570 
571     /***
572      * Deletes all closed connections.  Only connections currently owned by the connection
573      * manager are processed.
574      * 
575      * @see HttpConnection#isOpen()
576      * 
577      * @since 3.0
578      */
579     public void deleteClosedConnections() {
580         connectionPool.deleteClosedConnections();
581     }
582     
583     /***
584      * @since 3.0
585      */
586     public void closeIdleConnections(long idleTimeout) {
587         connectionPool.closeIdleConnections(idleTimeout);
588     }
589     
590     /***
591      * Make the given HttpConnection available for use by other requests.
592      * If another thread is blocked in getConnection() that could use this
593      * connection, it will be woken up.
594      *
595      * @param conn the HttpConnection to make available.
596      */
597     public void releaseConnection(HttpConnection conn) {
598         LOG.trace("enter HttpConnectionManager.releaseConnection(HttpConnection)");
599 
600         if (conn instanceof HttpConnectionAdapter) {
601             // connections given out are wrapped in an HttpConnectionAdapter
602             conn = ((HttpConnectionAdapter) conn).getWrappedConnection();
603         } else {
604             // this is okay, when an HttpConnectionAdapter is released
605             // is releases the real connection
606         }
607 
608         // make sure that the response has been read.
609         SimpleHttpConnectionManager.finishLastResponse(conn);
610 
611         connectionPool.freeConnection(conn);
612     }
613 
614     /***
615      * Gets the host configuration for a connection.
616      * @param conn the connection to get the configuration of
617      * @return a new HostConfiguration
618      */
619     private HostConfiguration configurationForConnection(HttpConnection conn) {
620 
621         HostConfiguration connectionConfiguration = new HostConfiguration();
622         
623         connectionConfiguration.setHost(
624             conn.getHost(), 
625             conn.getVirtualHost(), 
626             conn.getPort(), 
627             conn.getProtocol()
628         );
629         if (conn.getLocalAddress() != null) {
630             connectionConfiguration.setLocalAddress(conn.getLocalAddress());
631         }
632         if (conn.getProxyHost() != null) {
633             connectionConfiguration.setProxy(conn.getProxyHost(), conn.getProxyPort());
634         }
635 
636         return connectionConfiguration;
637     }
638 
639     /***
640      * Returns {@link HttpConnectionManagerParams parameters} associated 
641      * with this connection manager.
642      * 
643      * @since 3.0
644      * 
645      * @see HttpConnectionManagerParams
646      */
647     public HttpConnectionManagerParams getParams() {
648         return this.params;
649     }
650 
651     /***
652      * Assigns {@link HttpConnectionManagerParams parameters} for this 
653      * connection manager.
654      * 
655      * @since 3.0
656      * 
657      * @see HttpConnectionManagerParams
658      */
659     public void setParams(final HttpConnectionManagerParams params) {
660         if (params == null) {
661             throw new IllegalArgumentException("Parameters may not be null");
662         }
663         this.params = params;
664     }
665     
666     /***
667      * Global Connection Pool, including per-host pools
668      */
669     private class ConnectionPool {
670         
671         /*** The list of free connections */
672         private LinkedList freeConnections = new LinkedList();
673 
674         /*** The list of WaitingThreads waiting for a connection */
675         private LinkedList waitingThreads = new LinkedList();
676 
677         /***
678          * Map where keys are {@link HostConfiguration}s and values are {@link
679          * HostConnectionPool}s
680          */
681         private final Map mapHosts = new HashMap();
682 
683         private IdleConnectionHandler idleConnectionHandler = new IdleConnectionHandler();        
684         
685         /*** The number of created connections */
686         private int numConnections = 0;
687 
688         /***
689          * Cleans up all connection pool resources.
690          */
691         public synchronized void shutdown() {
692             
693             // close all free connections
694             Iterator iter = freeConnections.iterator();
695             while (iter.hasNext()) {
696                 HttpConnection conn = (HttpConnection) iter.next();
697                 iter.remove();
698                 conn.close();
699             }
700             
701             // close all connections that have been checked out
702             shutdownCheckedOutConnections(this);
703             
704             // interrupt all waiting threads
705             iter = waitingThreads.iterator();
706             while (iter.hasNext()) {
707                 WaitingThread waiter = (WaitingThread) iter.next();
708                 iter.remove();
709                 waiter.thread.interrupt();
710             }
711             
712             // clear out map hosts
713             mapHosts.clear();
714             
715             // remove all references to connections
716             idleConnectionHandler.removeAll();
717         }
718         
719         /***
720          * Creates a new connection and returns is for use of the calling method.
721          *
722          * @param hostConfiguration the configuration for the connection
723          * @return a new connection or <code>null</code> if none are available
724          */
725         public synchronized HttpConnection createConnection(HostConfiguration hostConfiguration) {
726             
727             HttpConnectionWithReference connection = null;
728 
729             HostConnectionPool hostPool = getHostPool(hostConfiguration);
730 
731             if ((hostPool.numConnections < getMaxConnectionsPerHost()) 
732                 && (numConnections < getMaxTotalConnections())) {
733 
734                 if (LOG.isDebugEnabled()) {
735                     LOG.debug("Allocating new connection, hostConfig=" + hostConfiguration);
736                 }
737                 connection = new HttpConnectionWithReference(hostConfiguration);
738                 connection.getParams().setDefaults(MultiThreadedHttpConnectionManager.this.params);
739                 connection.setHttpConnectionManager(MultiThreadedHttpConnectionManager.this);
740                 numConnections++;
741                 hostPool.numConnections++;
742         
743                 // store a reference to this connection so that it can be cleaned up
744                 // in the event it is not correctly released
745                 storeReferenceToConnection(connection, hostConfiguration, this);
746 
747             } else if (LOG.isDebugEnabled()) {
748                 if (hostPool.numConnections >= getMaxConnectionsPerHost()) {
749                     LOG.debug("No connection allocated, host pool has already reached "
750                         + "maxConnectionsPerHost, hostConfig=" + hostConfiguration
751                         + ", maxConnectionsPerhost=" + getMaxConnectionsPerHost());
752                 } else {
753                     LOG.debug("No connection allocated, maxTotalConnections reached, "
754                         + "maxTotalConnections=" + getMaxTotalConnections());
755                 }
756             }
757             
758             return connection;
759         }
760     
761         /***
762          * Handles cleaning up for a lost connection with the given config.  Decrements any 
763          * connection counts and notifies waiting threads, if appropriate.
764          * 
765          * @param config the host configuration of the connection that was lost
766          */
767         public synchronized void handleLostConnection(HostConfiguration config) {
768             HostConnectionPool hostPool = getHostPool(config);
769             hostPool.numConnections--;
770 
771             numConnections--;
772             notifyWaitingThread(config);
773         }
774 
775         /***
776          * Get the pool (list) of connections available for the given hostConfig.
777          *
778          * @param hostConfiguration the configuraton for the connection pool
779          * @return a pool (list) of connections available for the given config
780          */
781         public synchronized HostConnectionPool getHostPool(HostConfiguration hostConfiguration) {
782             LOG.trace("enter HttpConnectionManager.ConnectionPool.getHostPool(HostConfiguration)");
783 
784             // Look for a list of connections for the given config
785             HostConnectionPool listConnections = (HostConnectionPool) 
786                 mapHosts.get(hostConfiguration);
787             if (listConnections == null) {
788                 // First time for this config
789                 listConnections = new HostConnectionPool();
790                 listConnections.hostConfiguration = hostConfiguration;
791                 mapHosts.put(hostConfiguration, listConnections);
792             }
793             
794             return listConnections;
795         }
796 
797         /***
798          * If available, get a free connection for this host
799          *
800          * @param hostConfiguration the configuraton for the connection pool
801          * @return an available connection for the given config
802          */
803         public synchronized HttpConnection getFreeConnection(HostConfiguration hostConfiguration) {
804 
805             HttpConnectionWithReference connection = null;
806             
807             HostConnectionPool hostPool = getHostPool(hostConfiguration);
808 
809             if (hostPool.freeConnections.size() > 0) {
810                 connection = (HttpConnectionWithReference) hostPool.freeConnections.removeFirst();
811                 freeConnections.remove(connection);
812                 // store a reference to this connection so that it can be cleaned up
813                 // in the event it is not correctly released
814                 storeReferenceToConnection(connection, hostConfiguration, this);
815                 if (LOG.isDebugEnabled()) {
816                     LOG.debug("Getting free connection, hostConfig=" + hostConfiguration);
817                 }
818 
819                 // remove the connection from the timeout handler
820                 idleConnectionHandler.remove(connection);
821             } else if (LOG.isDebugEnabled()) {
822                 LOG.debug("There were no free connections to get, hostConfig=" 
823                     + hostConfiguration);
824             }
825             return connection;
826         }
827         
828         /***
829          * Deletes all closed connections.
830          */        
831         public synchronized void deleteClosedConnections() {
832             
833             Iterator iter = freeConnections.iterator();
834             
835             while (iter.hasNext()) {
836                 HttpConnection conn = (HttpConnection) iter.next();
837                 if (!conn.isOpen()) {
838                     iter.remove();
839                     deleteConnection(conn);
840                 }
841             }
842         }
843 
844         /***
845          * Closes idle connections.
846          * @param idleTimeout
847          */
848         public synchronized void closeIdleConnections(long idleTimeout) {
849             idleConnectionHandler.closeIdleConnections(idleTimeout);
850         }
851         
852         /***
853          * Deletes the given connection.  This will remove all reference to the connection
854          * so that it can be GCed.
855          * 
856          * <p><b>Note:</b> Does not remove the connection from the freeConnections list.  It
857          * is assumed that the caller has already handled this case.</p>
858          * 
859          * @param connection The connection to delete
860          */
861         private synchronized void deleteConnection(HttpConnection connection) {
862             
863             HostConfiguration connectionConfiguration = configurationForConnection(connection);
864 
865             if (LOG.isDebugEnabled()) {
866                 LOG.debug("Reclaiming connection, hostConfig=" + connectionConfiguration);
867             }
868 
869             connection.close();
870 
871             HostConnectionPool hostPool = getHostPool(connectionConfiguration);
872             
873             hostPool.freeConnections.remove(connection);
874             hostPool.numConnections--;
875             numConnections--;
876 
877             // remove the connection from the timeout handler
878             idleConnectionHandler.remove(connection);            
879         }
880         
881         /***
882          * Close and delete an old, unused connection to make room for a new one.
883          */
884         public synchronized void deleteLeastUsedConnection() {
885 
886             HttpConnection connection = (HttpConnection) freeConnections.removeFirst();
887 
888             if (connection != null) {
889                 deleteConnection(connection);
890             } else if (LOG.isDebugEnabled()) {
891                 LOG.debug("Attempted to reclaim an unused connection but there were none.");
892             }
893         }
894 
895         /***
896          * Notifies a waiting thread that a connection for the given configuration is 
897          * available.
898          * @param configuration the host config to use for notifying
899          * @see #notifyWaitingThread(HostConnectionPool)
900          */
901         public synchronized void notifyWaitingThread(HostConfiguration configuration) {
902             notifyWaitingThread(getHostPool(configuration));
903         }
904 
905         /***
906          * Notifies a waiting thread that a connection for the given configuration is 
907          * available.  This will wake a thread witing in tis hostPool or if there is not
908          * one a thread in the ConnectionPool will be notified.
909          * 
910          * @param hostPool the host pool to use for notifying
911          */
912         public synchronized void notifyWaitingThread(HostConnectionPool hostPool) {
913 
914             // find the thread we are going to notify, we want to ensure that each
915             // waiting thread is only interrupted once so we will remove it from 
916             // all wait queues before interrupting it
917             WaitingThread waitingThread = null;
918                 
919             if (hostPool.waitingThreads.size() > 0) {
920                 if (LOG.isDebugEnabled()) {
921                     LOG.debug("Notifying thread waiting on host pool, hostConfig=" 
922                         + hostPool.hostConfiguration);
923                 }                
924                 waitingThread = (WaitingThread) hostPool.waitingThreads.removeFirst();
925                 waitingThreads.remove(waitingThread);
926             } else if (waitingThreads.size() > 0) {
927                 if (LOG.isDebugEnabled()) {
928                     LOG.debug("No-one waiting on host pool, notifying next waiting thread.");
929                 }
930                 waitingThread = (WaitingThread) waitingThreads.removeFirst();
931                 waitingThread.hostConnectionPool.waitingThreads.remove(waitingThread);
932             } else if (LOG.isDebugEnabled()) {
933                 LOG.debug("Notifying no-one, there are no waiting threads");
934             }
935                 
936             if (waitingThread != null) {
937                 waitingThread.thread.interrupt();
938             }
939         }
940 
941         /***
942          * Marks the given connection as free.
943          * @param conn a connection that is no longer being used
944          */
945         public void freeConnection(HttpConnection conn) {
946 
947             HostConfiguration connectionConfiguration = configurationForConnection(conn);
948 
949             if (LOG.isDebugEnabled()) {
950                 LOG.debug("Freeing connection, hostConfig=" + connectionConfiguration);
951             }
952 
953             synchronized (this) {
954                 
955                 if (shutdown) {
956                     // the connection manager has been shutdown, release the connection's
957                     // resources and get out of here
958                     conn.close();
959                     return;
960                 }
961                 
962                 HostConnectionPool hostPool = getHostPool(connectionConfiguration);
963 
964                 // Put the connect back in the available list and notify a waiter
965                 hostPool.freeConnections.add(conn);
966                 if (hostPool.numConnections == 0) {
967                     // for some reason this connection pool didn't already exist
968                     LOG.error("Host connection pool not found, hostConfig=" 
969                               + connectionConfiguration);
970                     hostPool.numConnections = 1;
971                 }
972 
973                 freeConnections.add(conn);
974                 // we can remove the reference to this connection as we have control over
975                 // it again.  this also ensures that the connection manager can be GCed
976                 removeReferenceToConnection((HttpConnectionWithReference) conn);
977                 if (numConnections == 0) {
978                     // for some reason this connection pool didn't already exist
979                     LOG.error("Host connection pool not found, hostConfig=" 
980                               + connectionConfiguration);
981                     numConnections = 1;
982                 }
983 
984                 // register the connection with the timeout handler
985                 idleConnectionHandler.add(conn);
986 
987                 notifyWaitingThread(hostPool);
988             }
989         }
990     }
991 
992     /***
993      * A simple struct-like class to combine the objects needed to release a connection's
994      * resources when claimed by the garbage collector.
995      */
996     private static class ConnectionSource {
997         
998         /*** The connection pool that created the connection */
999         public ConnectionPool connectionPool;
1000 
1001         /*** The connection's host configuration */
1002         public HostConfiguration hostConfiguration;
1003     }
1004     
1005     /***
1006      * A simple struct-like class to combine the connection list and the count
1007      * of created connections.
1008      */
1009     private static class HostConnectionPool {
1010         /*** The hostConfig this pool is for */
1011         public HostConfiguration hostConfiguration;
1012         
1013         /*** The list of free connections */
1014         public LinkedList freeConnections = new LinkedList();
1015         
1016         /*** The list of WaitingThreads for this host */
1017         public LinkedList waitingThreads = new LinkedList();
1018 
1019         /*** The number of created connections */
1020         public int numConnections = 0;
1021     }
1022     
1023     /***
1024      * A simple struct-like class to combine the waiting thread and the connection 
1025      * pool it is waiting on.
1026      */
1027     private static class WaitingThread {
1028         /*** The thread that is waiting for a connection */
1029         public Thread thread;
1030         
1031         /*** The connection pool the thread is waiting for */
1032         public HostConnectionPool hostConnectionPool;
1033     }
1034 
1035     /***
1036      * A thread for listening for HttpConnections reclaimed by the garbage
1037      * collector.
1038      */
1039     private static class ReferenceQueueThread extends Thread {
1040 
1041         private boolean shutdown = false;
1042         
1043         /***
1044          * Create an instance and make this a daemon thread.
1045          */
1046         public ReferenceQueueThread() {
1047             setDaemon(true);
1048             setName("MultiThreadedHttpConnectionManager cleanup");
1049         }
1050 
1051         public void shutdown() {
1052             this.shutdown = true;
1053         }
1054         
1055         /***
1056          * Handles cleaning up for the given connection reference.
1057          * 
1058          * @param ref the reference to clean up
1059          */
1060         private void handleReference(Reference ref) {
1061             
1062             ConnectionSource source = null;
1063             
1064             synchronized (REFERENCE_TO_CONNECTION_SOURCE) {
1065                 source = (ConnectionSource) REFERENCE_TO_CONNECTION_SOURCE.remove(ref);
1066             }
1067             // only clean up for this reference if it is still associated with 
1068             // a ConnectionSource
1069             if (source != null) {
1070                 if (LOG.isDebugEnabled()) {
1071                     LOG.debug(
1072                         "Connection reclaimed by garbage collector, hostConfig=" 
1073                         + source.hostConfiguration);
1074                 }
1075                 
1076                 source.connectionPool.handleLostConnection(source.hostConfiguration);
1077             }
1078         }
1079 
1080         /***
1081          * Start execution.
1082          */
1083         public void run() {
1084             while (!shutdown) {
1085                 try {
1086                     // remove the next reference and process it, a timeout 
1087                     // is used so that the thread does not block indefinitely 
1088                     // and therefore keep the thread from shutting down
1089                     Reference ref = REFERENCE_QUEUE.remove(1000);
1090                     if (ref != null) {
1091                         handleReference(ref);
1092                     }
1093                 } catch (InterruptedException e) {
1094                     LOG.debug("ReferenceQueueThread interrupted", e);
1095                 }
1096             }
1097         }
1098 
1099     }
1100     
1101     /***
1102      * A connection that keeps a reference to itself.
1103      */
1104     private static class HttpConnectionWithReference extends HttpConnection {
1105         
1106         public WeakReference reference = new WeakReference(this, REFERENCE_QUEUE);
1107         
1108         /***
1109          * @param hostConfiguration
1110          */
1111         public HttpConnectionWithReference(HostConfiguration hostConfiguration) {
1112             super(hostConfiguration);
1113         }
1114 
1115     }
1116     
1117     /***
1118      * An HttpConnection wrapper that ensures a connection cannot be used
1119      * once released.
1120      */
1121     private static class HttpConnectionAdapter extends HttpConnection {
1122 
1123         // the wrapped connection
1124         private HttpConnection wrappedConnection;
1125 
1126         /***
1127          * Creates a new HttpConnectionAdapter.
1128          * @param connection the connection to be wrapped
1129          */
1130         public HttpConnectionAdapter(HttpConnection connection) {
1131             super(connection.getHost(), connection.getPort(), connection.getProtocol());
1132             this.wrappedConnection = connection;
1133         }
1134 
1135         /***
1136          * Tests if the wrapped connection is still available.
1137          * @return boolean
1138          */
1139         protected boolean hasConnection() {
1140             return wrappedConnection != null;
1141         }
1142 
1143         /***
1144          * @return HttpConnection
1145          */
1146         HttpConnection getWrappedConnection() {
1147             return wrappedConnection;
1148         }
1149         
1150         public void close() {
1151             if (hasConnection()) {
1152                 wrappedConnection.close();
1153             } else {
1154                 // do nothing
1155             }
1156         }
1157 
1158         public InetAddress getLocalAddress() {
1159             if (hasConnection()) {
1160                 return wrappedConnection.getLocalAddress();
1161             } else {
1162                 return null;
1163             }
1164         }
1165 
1166         /***
1167          * @deprecated
1168          */
1169         public boolean isStaleCheckingEnabled() {
1170             if (hasConnection()) {
1171                 return wrappedConnection.isStaleCheckingEnabled();
1172             } else {
1173                 return false;
1174             }
1175         }
1176 
1177         public void setLocalAddress(InetAddress localAddress) {
1178             if (hasConnection()) {
1179                 wrappedConnection.setLocalAddress(localAddress);
1180             } else {
1181                 throw new IllegalStateException("Connection has been released");
1182             }
1183         }
1184     
1185         /***
1186          * @deprecated 
1187          */
1188         public void setStaleCheckingEnabled(boolean staleCheckEnabled) {
1189             if (hasConnection()) {
1190                 wrappedConnection.setStaleCheckingEnabled(staleCheckEnabled);
1191             } else {
1192                 throw new IllegalStateException("Connection has been released");
1193             }
1194         }
1195 
1196         public String getHost() {
1197             if (hasConnection()) {
1198                 return wrappedConnection.getHost();
1199             } else {
1200                 return null;
1201             }
1202         }
1203 
1204         public HttpConnectionManager getHttpConnectionManager() {
1205             if (hasConnection()) {
1206                 return wrappedConnection.getHttpConnectionManager();
1207             } else {
1208                 return null;
1209             }
1210         }
1211 
1212         public InputStream getLastResponseInputStream() {
1213             if (hasConnection()) {
1214                 return wrappedConnection.getLastResponseInputStream();
1215             } else {
1216                 return null;
1217             }
1218         }
1219 
1220         public int getPort() {
1221             if (hasConnection()) {
1222                 return wrappedConnection.getPort();
1223             } else {
1224                 return -1;
1225             }
1226         }
1227 
1228         public Protocol getProtocol() {
1229             if (hasConnection()) {
1230                 return wrappedConnection.getProtocol();
1231             } else {
1232                 return null;
1233             }
1234         }
1235 
1236         public String getProxyHost() {
1237             if (hasConnection()) {
1238                 return wrappedConnection.getProxyHost();
1239             } else {
1240                 return null;
1241             }
1242         }
1243 
1244         public int getProxyPort() {
1245             if (hasConnection()) {
1246                 return wrappedConnection.getProxyPort();
1247             } else {
1248                 return -1;
1249             }
1250         }
1251 
1252         public OutputStream getRequestOutputStream()
1253             throws IOException, IllegalStateException {
1254             if (hasConnection()) {
1255                 return wrappedConnection.getRequestOutputStream();
1256             } else {
1257                 return null;
1258             }
1259         }
1260 
1261         public InputStream getResponseInputStream()
1262             throws IOException, IllegalStateException {
1263             if (hasConnection()) {
1264                 return wrappedConnection.getResponseInputStream();
1265             } else {
1266                 return null;
1267             }
1268         }
1269 
1270         public boolean isOpen() {
1271             if (hasConnection()) {
1272                 return wrappedConnection.isOpen();
1273             } else {
1274                 return false;
1275             }
1276         }
1277 
1278         public boolean closeIfStale() {
1279             if (hasConnection()) {
1280                 return wrappedConnection.closeIfStale();
1281             } else {
1282                 return false;
1283             }
1284         }
1285 
1286         public boolean isProxied() {
1287             if (hasConnection()) {
1288                 return wrappedConnection.isProxied();
1289             } else {
1290                 return false;
1291             }
1292         }
1293 
1294         public boolean isResponseAvailable() throws IOException {
1295             if (hasConnection()) {
1296                 return  wrappedConnection.isResponseAvailable();
1297             } else {
1298                 return false;
1299             }
1300         }
1301 
1302         public boolean isResponseAvailable(int timeout) throws IOException {
1303             if (hasConnection()) {
1304                 return  wrappedConnection.isResponseAvailable(timeout);
1305             } else {
1306                 return false;
1307             }
1308         }
1309 
1310         public boolean isSecure() {
1311             if (hasConnection()) {
1312                 return wrappedConnection.isSecure();
1313             } else {
1314                 return false;
1315             }
1316         }
1317 
1318         public boolean isTransparent() {
1319             if (hasConnection()) {
1320                 return wrappedConnection.isTransparent();
1321             } else {
1322                 return false;
1323             }
1324         }
1325 
1326         public void open() throws IOException {
1327             if (hasConnection()) {
1328                 wrappedConnection.open();
1329             } else {
1330                 throw new IllegalStateException("Connection has been released");
1331             }
1332         }
1333 
1334         /***
1335          * @deprecated
1336          */
1337         public void print(String data)
1338             throws IOException, IllegalStateException, HttpRecoverableException {
1339             if (hasConnection()) {
1340                 wrappedConnection.print(data);
1341             } else {
1342                 throw new IllegalStateException("Connection has been released");
1343             }
1344         }
1345 
1346         public void printLine()
1347             throws IOException, IllegalStateException, HttpRecoverableException {
1348             if (hasConnection()) {
1349                 wrappedConnection.printLine();
1350             } else {
1351                 throw new IllegalStateException("Connection has been released");
1352             }
1353         }
1354 
1355         /***
1356          * @deprecated
1357          */
1358         public void printLine(String data)
1359             throws IOException, IllegalStateException, HttpRecoverableException {
1360             if (hasConnection()) {
1361                 wrappedConnection.printLine(data);
1362             } else {
1363                 throw new IllegalStateException("Connection has been released");
1364             }
1365         }
1366 
1367         /***
1368          * @deprecated
1369          */
1370         public String readLine() throws IOException, IllegalStateException {
1371             if (hasConnection()) {
1372                 return wrappedConnection.readLine();
1373             } else {
1374                 throw new IllegalStateException("Connection has been released");
1375             }
1376         }
1377 
1378         public String readLine(String charset) throws IOException, IllegalStateException {
1379             if (hasConnection()) {
1380                 return wrappedConnection.readLine(charset);
1381             } else {
1382                 throw new IllegalStateException("Connection has been released");
1383             }
1384         }
1385 
1386         public void releaseConnection() {
1387             if (!isLocked() && hasConnection()) {
1388                 HttpConnection wrappedConnection = this.wrappedConnection;
1389                 this.wrappedConnection = null;
1390                 wrappedConnection.releaseConnection();
1391             } else {
1392                 // do nothing
1393             }
1394         }
1395 
1396         /***
1397          * @deprecated
1398          */
1399         public void setConnectionTimeout(int timeout) {
1400             if (hasConnection()) {
1401                 wrappedConnection.setConnectionTimeout(timeout);
1402             } else {
1403                 // do nothing
1404             }
1405         }
1406 
1407         public void setHost(String host) throws IllegalStateException {
1408             if (hasConnection()) {
1409                 wrappedConnection.setHost(host);
1410             } else {
1411                 // do nothing
1412             }
1413         }
1414 
1415         public void setHttpConnectionManager(HttpConnectionManager httpConnectionManager) {
1416             if (hasConnection()) {
1417                 wrappedConnection.setHttpConnectionManager(httpConnectionManager);
1418             } else {
1419                 // do nothing
1420             }
1421         }
1422 
1423         public void setLastResponseInputStream(InputStream inStream) {
1424             if (hasConnection()) {
1425                 wrappedConnection.setLastResponseInputStream(inStream);
1426             } else {
1427                 // do nothing
1428             }
1429         }
1430 
1431         public void setPort(int port) throws IllegalStateException {
1432             if (hasConnection()) {
1433                 wrappedConnection.setPort(port);
1434             } else {
1435                 // do nothing
1436             }
1437         }
1438 
1439         public void setProtocol(Protocol protocol) {
1440             if (hasConnection()) {
1441                 wrappedConnection.setProtocol(protocol);
1442             } else {
1443                 // do nothing
1444             }
1445         }
1446 
1447         public void setProxyHost(String host) throws IllegalStateException {
1448             if (hasConnection()) {
1449                 wrappedConnection.setProxyHost(host);
1450             } else {
1451                 // do nothing
1452             }
1453         }
1454 
1455         public void setProxyPort(int port) throws IllegalStateException {
1456             if (hasConnection()) {
1457                 wrappedConnection.setProxyPort(port);
1458             } else {
1459                 // do nothing
1460             }
1461         }
1462 
1463         /***
1464          * @deprecated
1465          */
1466         public void setSoTimeout(int timeout)
1467             throws SocketException, IllegalStateException {
1468             if (hasConnection()) {
1469                 wrappedConnection.setSoTimeout(timeout);
1470             } else {
1471                 // do nothing
1472             }
1473         }
1474 
1475         /***
1476          * @deprecated
1477          */
1478         public void shutdownOutput() {
1479             if (hasConnection()) {
1480                 wrappedConnection.shutdownOutput();
1481             } else {
1482                 // do nothing
1483             }
1484         }
1485 
1486         public void tunnelCreated() throws IllegalStateException, IOException {
1487             if (hasConnection()) {
1488                 wrappedConnection.tunnelCreated();
1489             } else {
1490                 // do nothing
1491             }
1492         }
1493 
1494         public void write(byte[] data, int offset, int length)
1495             throws IOException, IllegalStateException, HttpRecoverableException {
1496             if (hasConnection()) {
1497                 wrappedConnection.write(data, offset, length);
1498             } else {
1499                 throw new IllegalStateException("Connection has been released");
1500             }
1501         }
1502 
1503         public void write(byte[] data)
1504             throws IOException, IllegalStateException, HttpRecoverableException {
1505             if (hasConnection()) {
1506                 wrappedConnection.write(data);
1507             } else {
1508                 throw new IllegalStateException("Connection has been released");
1509             }
1510         }
1511 
1512         public void writeLine()
1513             throws IOException, IllegalStateException, HttpRecoverableException {
1514             if (hasConnection()) {
1515                 wrappedConnection.writeLine();
1516             } else {
1517                 throw new IllegalStateException("Connection has been released");
1518             }
1519         }
1520 
1521         public void writeLine(byte[] data)
1522             throws IOException, IllegalStateException, HttpRecoverableException {
1523             if (hasConnection()) {
1524                 wrappedConnection.writeLine(data);
1525             } else {
1526                 throw new IllegalStateException("Connection has been released");
1527             }
1528         }
1529 
1530         public void flushRequestOutputStream() throws IOException {
1531             if (hasConnection()) {
1532                 wrappedConnection.flushRequestOutputStream();
1533             } else {
1534                 throw new IllegalStateException("Connection has been released");
1535             }
1536         }
1537 
1538         /***
1539          * @deprecated
1540          */
1541         public int getSoTimeout() throws SocketException {
1542             if (hasConnection()) {
1543                 return wrappedConnection.getSoTimeout();
1544             } else {
1545                 throw new IllegalStateException("Connection has been released");
1546             }
1547         }
1548 
1549         public String getVirtualHost() {
1550             if (hasConnection()) {
1551                 return wrappedConnection.getVirtualHost();
1552             } else {
1553                 throw new IllegalStateException("Connection has been released");
1554             }
1555         }
1556 
1557         public void setVirtualHost(String host) throws IllegalStateException {
1558             if (hasConnection()) {
1559                 wrappedConnection.setVirtualHost(host);
1560             } else {
1561                 throw new IllegalStateException("Connection has been released");
1562             }
1563         }
1564 
1565         public int getSendBufferSize() throws SocketException {
1566             if (hasConnection()) {
1567                 return wrappedConnection.getSendBufferSize();
1568             } else {
1569                 throw new IllegalStateException("Connection has been released");
1570             }
1571         }
1572 
1573         /***
1574          * @deprecated
1575          */
1576         public void setSendBufferSize(int sendBufferSize) throws SocketException {
1577             if (hasConnection()) {
1578                 wrappedConnection.setSendBufferSize(sendBufferSize);
1579             } else {
1580                 throw new IllegalStateException("Connection has been released");
1581             }
1582         }
1583 
1584         public HttpConnectionParams getParams() {
1585             if (hasConnection()) {
1586                 return wrappedConnection.getParams();
1587             } else {
1588                 throw new IllegalStateException("Connection has been released");
1589             }
1590         }
1591 
1592         public void setParams(final HttpConnectionParams params) {
1593             if (hasConnection()) {
1594                 wrappedConnection.setParams(params);
1595             } else {
1596                 throw new IllegalStateException("Connection has been released");
1597             }
1598         }
1599 
1600         /* (non-Javadoc)
1601          * @see org.apache.commons.httpclient.HttpConnection#print(java.lang.String, java.lang.String)
1602          */
1603         public void print(String data, String charset) throws IOException, IllegalStateException {
1604             if (hasConnection()) {
1605                 wrappedConnection.print(data, charset);
1606             } else {
1607                 throw new IllegalStateException("Connection has been released");
1608             }
1609         }
1610 
1611         /* (non-Javadoc)
1612          * @see org.apache.commons.httpclient.HttpConnection#printLine(java.lang.String, java.lang.String)
1613          */
1614         public void printLine(String data, String charset)
1615             throws IOException, IllegalStateException {
1616             if (hasConnection()) {
1617                 wrappedConnection.printLine(data, charset);
1618             } else {
1619                 throw new IllegalStateException("Connection has been released");
1620             }
1621         }
1622 
1623         /* (non-Javadoc)
1624          * @see org.apache.commons.httpclient.HttpConnection#setSocketTimeout(int)
1625          */
1626         public void setSocketTimeout(int timeout) throws SocketException, IllegalStateException {
1627             if (hasConnection()) {
1628                 wrappedConnection.setSocketTimeout(timeout);
1629             } else {
1630                 throw new IllegalStateException("Connection has been released");
1631             }
1632         }
1633 
1634     }
1635 
1636 }
1637