View Javadoc

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