View Javadoc

1   /*
2    * $HeadURL: https://svn.apache.org/repos/asf/httpcomponents/oac.hc3x/trunk/src/java/org/apache/commons/httpclient/MultiThreadedHttpConnectionManager.java $
3    * $Revision: 1425331 $
4    * $Date: 2012-12-22 18:29:41 +0000 (Sat, 22 Dec 2012) $
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, true);
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, false);
561             return (hostPool != null) ? hostPool.numConnections : 0;
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, true);
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, true);
783             hostPool.numConnections--;
784             if ((hostPool.numConnections == 0) &&
785                 hostPool.waitingThreads.isEmpty()) {
786 
787                 mapHosts.remove(config);
788             }
789             
790             numConnections--;
791             notifyWaitingThread(config);
792         }
793 
794         /***
795          * Get the pool (list) of connections available for the given hostConfig.
796          *
797          * @param hostConfiguration the configuraton for the connection pool
798          * @param create <code>true</code> to create a pool if not found,
799          *               <code>false</code> to return <code>null</code>
800          *
801          * @return a pool (list) of connections available for the given config,
802          *         or <code>null</code> if neither found nor created
803          */
804         public synchronized HostConnectionPool getHostPool(HostConfiguration hostConfiguration, boolean create) {
805             LOG.trace("enter HttpConnectionManager.ConnectionPool.getHostPool(HostConfiguration)");
806 
807             // Look for a list of connections for the given config
808             HostConnectionPool listConnections = (HostConnectionPool) 
809                 mapHosts.get(hostConfiguration);
810             if ((listConnections == null) && create) {
811                 // First time for this config
812                 listConnections = new HostConnectionPool();
813                 listConnections.hostConfiguration = hostConfiguration;
814                 mapHosts.put(hostConfiguration, listConnections);
815             }
816             
817             return listConnections;
818         }
819 
820         /***
821          * If available, get a free connection for this host
822          *
823          * @param hostConfiguration the configuraton for the connection pool
824          * @return an available connection for the given config
825          */
826         public synchronized HttpConnection getFreeConnection(HostConfiguration hostConfiguration) {
827 
828             HttpConnectionWithReference connection = null;
829             
830             HostConnectionPool hostPool = getHostPool(hostConfiguration, false);
831 
832             if ((hostPool != null) && (hostPool.freeConnections.size() > 0)) {
833                 connection = (HttpConnectionWithReference) hostPool.freeConnections.removeLast();
834                 freeConnections.remove(connection);
835                 // store a reference to this connection so that it can be cleaned up
836                 // in the event it is not correctly released
837                 storeReferenceToConnection(connection, hostConfiguration, this);
838                 if (LOG.isDebugEnabled()) {
839                     LOG.debug("Getting free connection, hostConfig=" + hostConfiguration);
840                 }
841 
842                 // remove the connection from the timeout handler
843                 idleConnectionHandler.remove(connection);
844             } else if (LOG.isDebugEnabled()) {
845                 LOG.debug("There were no free connections to get, hostConfig=" 
846                     + hostConfiguration);
847             }
848             return connection;
849         }
850         
851         /***
852          * Deletes all closed connections.
853          */        
854         public synchronized void deleteClosedConnections() {
855             
856             Iterator iter = freeConnections.iterator();
857             
858             while (iter.hasNext()) {
859                 HttpConnection conn = (HttpConnection) iter.next();
860                 if (!conn.isOpen()) {
861                     iter.remove();
862                     deleteConnection(conn);
863                 }
864             }
865         }
866 
867         /***
868          * Closes idle connections.
869          * @param idleTimeout
870          */
871         public synchronized void closeIdleConnections(long idleTimeout) {
872             idleConnectionHandler.closeIdleConnections(idleTimeout);
873         }
874         
875         /***
876          * Deletes the given connection.  This will remove all reference to the connection
877          * so that it can be GCed.
878          * 
879          * <p><b>Note:</b> Does not remove the connection from the freeConnections list.  It
880          * is assumed that the caller has already handled this case.</p>
881          * 
882          * @param connection The connection to delete
883          */
884         private synchronized void deleteConnection(HttpConnection connection) {
885             
886             HostConfiguration connectionConfiguration = configurationForConnection(connection);
887 
888             if (LOG.isDebugEnabled()) {
889                 LOG.debug("Reclaiming connection, hostConfig=" + connectionConfiguration);
890             }
891 
892             connection.close();
893 
894             HostConnectionPool hostPool = getHostPool(connectionConfiguration, true);
895             
896             hostPool.freeConnections.remove(connection);
897             hostPool.numConnections--;
898             numConnections--;
899             if ((hostPool.numConnections == 0) &&
900                 hostPool.waitingThreads.isEmpty()) {
901 
902                 mapHosts.remove(connectionConfiguration);
903             }
904             
905             // remove the connection from the timeout handler
906             idleConnectionHandler.remove(connection);            
907         }
908         
909         /***
910          * Close and delete an old, unused connection to make room for a new one.
911          */
912         public synchronized void deleteLeastUsedConnection() {
913 
914             HttpConnection connection = (HttpConnection) freeConnections.removeFirst();
915 
916             if (connection != null) {
917                 deleteConnection(connection);
918             } else if (LOG.isDebugEnabled()) {
919                 LOG.debug("Attempted to reclaim an unused connection but there were none.");
920             }
921         }
922 
923         /***
924          * Notifies a waiting thread that a connection for the given configuration is 
925          * available.
926          * @param configuration the host config to use for notifying
927          * @see #notifyWaitingThread(HostConnectionPool)
928          */
929         public synchronized void notifyWaitingThread(HostConfiguration configuration) {
930             notifyWaitingThread(getHostPool(configuration, true));
931         }
932 
933         /***
934          * Notifies a waiting thread that a connection for the given configuration is 
935          * available.  This will wake a thread waiting in this host pool or if there is not
936          * one a thread in the connection pool will be notified.
937          * 
938          * @param hostPool the host pool to use for notifying
939          */
940         public synchronized void notifyWaitingThread(HostConnectionPool hostPool) {
941 
942             // find the thread we are going to notify, we want to ensure that each
943             // waiting thread is only interrupted once so we will remove it from 
944             // all wait queues before interrupting it
945             WaitingThread waitingThread = null;
946                 
947             if (hostPool.waitingThreads.size() > 0) {
948                 if (LOG.isDebugEnabled()) {
949                     LOG.debug("Notifying thread waiting on host pool, hostConfig=" 
950                         + hostPool.hostConfiguration);
951                 }
952                 waitingThread = (WaitingThread) hostPool.waitingThreads.removeFirst();
953                 waitingThreads.remove(waitingThread);
954             } else if (waitingThreads.size() > 0) {
955                 if (LOG.isDebugEnabled()) {
956                     LOG.debug("No-one waiting on host pool, notifying next waiting thread.");
957                 }
958                 waitingThread = (WaitingThread) waitingThreads.removeFirst();
959                 waitingThread.hostConnectionPool.waitingThreads.remove(waitingThread);
960             } else if (LOG.isDebugEnabled()) {
961                 LOG.debug("Notifying no-one, there are no waiting threads");
962             }
963                 
964             if (waitingThread != null) {
965                 waitingThread.interruptedByConnectionPool = true;
966                 waitingThread.thread.interrupt();
967             }
968         }
969 
970         /***
971          * Marks the given connection as free.
972          * @param conn a connection that is no longer being used
973          */
974         public void freeConnection(HttpConnection conn) {
975 
976             HostConfiguration connectionConfiguration = configurationForConnection(conn);
977 
978             if (LOG.isDebugEnabled()) {
979                 LOG.debug("Freeing connection, hostConfig=" + connectionConfiguration);
980             }
981 
982             synchronized (this) {
983                 
984                 if (shutdown) {
985                     // the connection manager has been shutdown, release the connection's
986                     // resources and get out of here
987                     conn.close();
988                     return;
989                 }
990                 
991                 HostConnectionPool hostPool = getHostPool(connectionConfiguration, true);
992 
993                 // Put the connect back in the available list and notify a waiter
994                 hostPool.freeConnections.add(conn);
995                 if (hostPool.numConnections == 0) {
996                     // for some reason this connection pool didn't already exist
997                     LOG.error("Host connection pool not found, hostConfig=" 
998                               + connectionConfiguration);
999                     hostPool.numConnections = 1;
1000                 }
1001 
1002                 freeConnections.add(conn);
1003                 // we can remove the reference to this connection as we have control over
1004                 // it again.  this also ensures that the connection manager can be GCed
1005                 removeReferenceToConnection((HttpConnectionWithReference) conn);
1006                 if (numConnections == 0) {
1007                     // for some reason this connection pool didn't already exist
1008                     LOG.error("Host connection pool not found, hostConfig=" 
1009                               + connectionConfiguration);
1010                     numConnections = 1;
1011                 }
1012 
1013                 // register the connection with the timeout handler
1014                 idleConnectionHandler.add(conn);
1015 
1016                 notifyWaitingThread(hostPool);
1017             }
1018         }
1019     }
1020 
1021     /***
1022      * A simple struct-like class to combine the objects needed to release a connection's
1023      * resources when claimed by the garbage collector.
1024      */
1025     private static class ConnectionSource {
1026         
1027         /*** The connection pool that created the connection */
1028         public ConnectionPool connectionPool;
1029 
1030         /*** The connection's host configuration */
1031         public HostConfiguration hostConfiguration;
1032     }
1033     
1034     /***
1035      * A simple struct-like class to combine the connection list and the count
1036      * of created connections.
1037      */
1038     private static class HostConnectionPool {
1039         /*** The hostConfig this pool is for */
1040         public HostConfiguration hostConfiguration;
1041         
1042         /*** The list of free connections */
1043         public LinkedList freeConnections = new LinkedList();
1044         
1045         /*** The list of WaitingThreads for this host */
1046         public LinkedList waitingThreads = new LinkedList();
1047 
1048         /*** The number of created connections */
1049         public int numConnections = 0;
1050     }
1051     
1052     /***
1053      * A simple struct-like class to combine the waiting thread and the connection 
1054      * pool it is waiting on.
1055      */
1056     private static class WaitingThread {
1057         /*** The thread that is waiting for a connection */
1058         public Thread thread;
1059         
1060         /*** The connection pool the thread is waiting for */
1061         public HostConnectionPool hostConnectionPool;
1062         
1063         /*** Flag to indicate if the thread was interrupted by the ConnectionPool. Set
1064          * to true inside {@link ConnectionPool#notifyWaitingThread(HostConnectionPool)} 
1065          * before the thread is interrupted. */
1066         public boolean interruptedByConnectionPool = false;
1067     }
1068 
1069     /***
1070      * A thread for listening for HttpConnections reclaimed by the garbage
1071      * collector.
1072      */
1073     private static class ReferenceQueueThread extends Thread {
1074 
1075         private volatile boolean shutdown = false;
1076         
1077         /***
1078          * Create an instance and make this a daemon thread.
1079          */
1080         public ReferenceQueueThread() {
1081             setDaemon(true);
1082             setName("MultiThreadedHttpConnectionManager cleanup");
1083         }
1084 
1085         public void shutdown() {
1086             this.shutdown = true;
1087             this.interrupt();
1088         }
1089         
1090         /***
1091          * Handles cleaning up for the given connection reference.
1092          * 
1093          * @param ref the reference to clean up
1094          */
1095         private void handleReference(Reference ref) {
1096             
1097             ConnectionSource source = null;
1098             
1099             synchronized (REFERENCE_TO_CONNECTION_SOURCE) {
1100                 source = (ConnectionSource) REFERENCE_TO_CONNECTION_SOURCE.remove(ref);
1101             }
1102             // only clean up for this reference if it is still associated with 
1103             // a ConnectionSource
1104             if (source != null) {
1105                 if (LOG.isDebugEnabled()) {
1106                     LOG.debug(
1107                         "Connection reclaimed by garbage collector, hostConfig=" 
1108                         + source.hostConfiguration);
1109                 }
1110                 
1111                 source.connectionPool.handleLostConnection(source.hostConfiguration);
1112             }
1113         }
1114 
1115         /***
1116          * Start execution.
1117          */
1118         public void run() {
1119             while (!shutdown) {
1120                 try {
1121                     // remove the next reference and process it
1122                     Reference ref = REFERENCE_QUEUE.remove();
1123                     if (ref != null) {
1124                         handleReference(ref);
1125                     }
1126                 } catch (InterruptedException e) {
1127                     LOG.debug("ReferenceQueueThread interrupted", e);
1128                 }
1129             }
1130         }
1131 
1132     }
1133     
1134     /***
1135      * A connection that keeps a reference to itself.
1136      */
1137     private static class HttpConnectionWithReference extends HttpConnection {
1138         
1139         public WeakReference reference = new WeakReference(this, REFERENCE_QUEUE);
1140         
1141         /***
1142          * @param hostConfiguration
1143          */
1144         public HttpConnectionWithReference(HostConfiguration hostConfiguration) {
1145             super(hostConfiguration);
1146         }
1147 
1148     }
1149     
1150     /***
1151      * An HttpConnection wrapper that ensures a connection cannot be used
1152      * once released.
1153      */
1154     private static class HttpConnectionAdapter extends HttpConnection {
1155 
1156         // the wrapped connection
1157         private HttpConnection wrappedConnection;
1158 
1159         /***
1160          * Creates a new HttpConnectionAdapter.
1161          * @param connection the connection to be wrapped
1162          */
1163         public HttpConnectionAdapter(HttpConnection connection) {
1164             super(connection.getHost(), connection.getPort(), connection.getProtocol());
1165             this.wrappedConnection = connection;
1166         }
1167 
1168         /***
1169          * Tests if the wrapped connection is still available.
1170          * @return boolean
1171          */
1172         protected boolean hasConnection() {
1173             return wrappedConnection != null;
1174         }
1175 
1176         /***
1177          * @return HttpConnection
1178          */
1179         HttpConnection getWrappedConnection() {
1180             return wrappedConnection;
1181         }
1182         
1183         public void close() {
1184             if (hasConnection()) {
1185                 wrappedConnection.close();
1186             } else {
1187                 // do nothing
1188             }
1189         }
1190 
1191         public InetAddress getLocalAddress() {
1192             if (hasConnection()) {
1193                 return wrappedConnection.getLocalAddress();
1194             } else {
1195                 return null;
1196             }
1197         }
1198 
1199         /***
1200          * @deprecated
1201          */
1202         public boolean isStaleCheckingEnabled() {
1203             if (hasConnection()) {
1204                 return wrappedConnection.isStaleCheckingEnabled();
1205             } else {
1206                 return false;
1207             }
1208         }
1209 
1210         public void setLocalAddress(InetAddress localAddress) {
1211             if (hasConnection()) {
1212                 wrappedConnection.setLocalAddress(localAddress);
1213             } else {
1214                 throw new IllegalStateException("Connection has been released");
1215             }
1216         }
1217     
1218         /***
1219          * @deprecated 
1220          */
1221         public void setStaleCheckingEnabled(boolean staleCheckEnabled) {
1222             if (hasConnection()) {
1223                 wrappedConnection.setStaleCheckingEnabled(staleCheckEnabled);
1224             } else {
1225                 throw new IllegalStateException("Connection has been released");
1226             }
1227         }
1228 
1229         public String getHost() {
1230             if (hasConnection()) {
1231                 return wrappedConnection.getHost();
1232             } else {
1233                 return null;
1234             }
1235         }
1236 
1237         public HttpConnectionManager getHttpConnectionManager() {
1238             if (hasConnection()) {
1239                 return wrappedConnection.getHttpConnectionManager();
1240             } else {
1241                 return null;
1242             }
1243         }
1244 
1245         public InputStream getLastResponseInputStream() {
1246             if (hasConnection()) {
1247                 return wrappedConnection.getLastResponseInputStream();
1248             } else {
1249                 return null;
1250             }
1251         }
1252 
1253         public int getPort() {
1254             if (hasConnection()) {
1255                 return wrappedConnection.getPort();
1256             } else {
1257                 return -1;
1258             }
1259         }
1260 
1261         public Protocol getProtocol() {
1262             if (hasConnection()) {
1263                 return wrappedConnection.getProtocol();
1264             } else {
1265                 return null;
1266             }
1267         }
1268 
1269         public String getProxyHost() {
1270             if (hasConnection()) {
1271                 return wrappedConnection.getProxyHost();
1272             } else {
1273                 return null;
1274             }
1275         }
1276 
1277         public int getProxyPort() {
1278             if (hasConnection()) {
1279                 return wrappedConnection.getProxyPort();
1280             } else {
1281                 return -1;
1282             }
1283         }
1284 
1285         public OutputStream getRequestOutputStream()
1286             throws IOException, IllegalStateException {
1287             if (hasConnection()) {
1288                 return wrappedConnection.getRequestOutputStream();
1289             } else {
1290                 return null;
1291             }
1292         }
1293 
1294         public InputStream getResponseInputStream()
1295             throws IOException, IllegalStateException {
1296             if (hasConnection()) {
1297                 return wrappedConnection.getResponseInputStream();
1298             } else {
1299                 return null;
1300             }
1301         }
1302 
1303         public boolean isOpen() {
1304             if (hasConnection()) {
1305                 return wrappedConnection.isOpen();
1306             } else {
1307                 return false;
1308             }
1309         }
1310 
1311         public boolean closeIfStale() throws IOException {
1312             if (hasConnection()) {
1313                 return wrappedConnection.closeIfStale();
1314             } else {
1315                 return false;
1316             }
1317         }
1318 
1319         public boolean isProxied() {
1320             if (hasConnection()) {
1321                 return wrappedConnection.isProxied();
1322             } else {
1323                 return false;
1324             }
1325         }
1326 
1327         public boolean isResponseAvailable() throws IOException {
1328             if (hasConnection()) {
1329                 return  wrappedConnection.isResponseAvailable();
1330             } else {
1331                 return false;
1332             }
1333         }
1334 
1335         public boolean isResponseAvailable(int timeout) throws IOException {
1336             if (hasConnection()) {
1337                 return  wrappedConnection.isResponseAvailable(timeout);
1338             } else {
1339                 return false;
1340             }
1341         }
1342 
1343         public boolean isSecure() {
1344             if (hasConnection()) {
1345                 return wrappedConnection.isSecure();
1346             } else {
1347                 return false;
1348             }
1349         }
1350 
1351         public boolean isTransparent() {
1352             if (hasConnection()) {
1353                 return wrappedConnection.isTransparent();
1354             } else {
1355                 return false;
1356             }
1357         }
1358 
1359         public void open() throws IOException {
1360             if (hasConnection()) {
1361                 wrappedConnection.open();
1362             } else {
1363                 throw new IllegalStateException("Connection has been released");
1364             }
1365         }
1366 
1367         /***
1368          * @deprecated
1369          */
1370         public void print(String data)
1371             throws IOException, IllegalStateException {
1372             if (hasConnection()) {
1373                 wrappedConnection.print(data);
1374             } else {
1375                 throw new IllegalStateException("Connection has been released");
1376             }
1377         }
1378 
1379         public void printLine()
1380             throws IOException, IllegalStateException {
1381             if (hasConnection()) {
1382                 wrappedConnection.printLine();
1383             } else {
1384                 throw new IllegalStateException("Connection has been released");
1385             }
1386         }
1387 
1388         /***
1389          * @deprecated
1390          */
1391         public void printLine(String data)
1392             throws IOException, IllegalStateException {
1393             if (hasConnection()) {
1394                 wrappedConnection.printLine(data);
1395             } else {
1396                 throw new IllegalStateException("Connection has been released");
1397             }
1398         }
1399 
1400         /***
1401          * @deprecated
1402          */
1403         public String readLine() throws IOException, IllegalStateException {
1404             if (hasConnection()) {
1405                 return wrappedConnection.readLine();
1406             } else {
1407                 throw new IllegalStateException("Connection has been released");
1408             }
1409         }
1410 
1411         public String readLine(String charset) throws IOException, IllegalStateException {
1412             if (hasConnection()) {
1413                 return wrappedConnection.readLine(charset);
1414             } else {
1415                 throw new IllegalStateException("Connection has been released");
1416             }
1417         }
1418 
1419         public void releaseConnection() {
1420             if (!isLocked() && hasConnection()) {
1421                 HttpConnection wrappedConnection = this.wrappedConnection;
1422                 this.wrappedConnection = null;
1423                 wrappedConnection.releaseConnection();
1424             } else {
1425                 // do nothing
1426             }
1427         }
1428 
1429         /***
1430          * @deprecated
1431          */
1432         public void setConnectionTimeout(int timeout) {
1433             if (hasConnection()) {
1434                 wrappedConnection.setConnectionTimeout(timeout);
1435             } else {
1436                 // do nothing
1437             }
1438         }
1439 
1440         public void setHost(String host) throws IllegalStateException {
1441             if (hasConnection()) {
1442                 wrappedConnection.setHost(host);
1443             } else {
1444                 // do nothing
1445             }
1446         }
1447 
1448         public void setHttpConnectionManager(HttpConnectionManager httpConnectionManager) {
1449             if (hasConnection()) {
1450                 wrappedConnection.setHttpConnectionManager(httpConnectionManager);
1451             } else {
1452                 // do nothing
1453             }
1454         }
1455 
1456         public void setLastResponseInputStream(InputStream inStream) {
1457             if (hasConnection()) {
1458                 wrappedConnection.setLastResponseInputStream(inStream);
1459             } else {
1460                 // do nothing
1461             }
1462         }
1463 
1464         public void setPort(int port) throws IllegalStateException {
1465             if (hasConnection()) {
1466                 wrappedConnection.setPort(port);
1467             } else {
1468                 // do nothing
1469             }
1470         }
1471 
1472         public void setProtocol(Protocol protocol) {
1473             if (hasConnection()) {
1474                 wrappedConnection.setProtocol(protocol);
1475             } else {
1476                 // do nothing
1477             }
1478         }
1479 
1480         public void setProxyHost(String host) throws IllegalStateException {
1481             if (hasConnection()) {
1482                 wrappedConnection.setProxyHost(host);
1483             } else {
1484                 // do nothing
1485             }
1486         }
1487 
1488         public void setProxyPort(int port) throws IllegalStateException {
1489             if (hasConnection()) {
1490                 wrappedConnection.setProxyPort(port);
1491             } else {
1492                 // do nothing
1493             }
1494         }
1495 
1496         /***
1497          * @deprecated
1498          */
1499         public void setSoTimeout(int timeout)
1500             throws SocketException, IllegalStateException {
1501             if (hasConnection()) {
1502                 wrappedConnection.setSoTimeout(timeout);
1503             } else {
1504                 // do nothing
1505             }
1506         }
1507 
1508         /***
1509          * @deprecated
1510          */
1511         public void shutdownOutput() {
1512             if (hasConnection()) {
1513                 wrappedConnection.shutdownOutput();
1514             } else {
1515                 // do nothing
1516             }
1517         }
1518 
1519         public void tunnelCreated() throws IllegalStateException, IOException {
1520             if (hasConnection()) {
1521                 wrappedConnection.tunnelCreated();
1522             } else {
1523                 // do nothing
1524             }
1525         }
1526 
1527         public void write(byte[] data, int offset, int length)
1528             throws IOException, IllegalStateException {
1529             if (hasConnection()) {
1530                 wrappedConnection.write(data, offset, length);
1531             } else {
1532                 throw new IllegalStateException("Connection has been released");
1533             }
1534         }
1535 
1536         public void write(byte[] data)
1537             throws IOException, IllegalStateException {
1538             if (hasConnection()) {
1539                 wrappedConnection.write(data);
1540             } else {
1541                 throw new IllegalStateException("Connection has been released");
1542             }
1543         }
1544 
1545         public void writeLine()
1546             throws IOException, IllegalStateException {
1547             if (hasConnection()) {
1548                 wrappedConnection.writeLine();
1549             } else {
1550                 throw new IllegalStateException("Connection has been released");
1551             }
1552         }
1553 
1554         public void writeLine(byte[] data)
1555             throws IOException, IllegalStateException {
1556             if (hasConnection()) {
1557                 wrappedConnection.writeLine(data);
1558             } else {
1559                 throw new IllegalStateException("Connection has been released");
1560             }
1561         }
1562 
1563         public void flushRequestOutputStream() throws IOException {
1564             if (hasConnection()) {
1565                 wrappedConnection.flushRequestOutputStream();
1566             } else {
1567                 throw new IllegalStateException("Connection has been released");
1568             }
1569         }
1570 
1571         /***
1572          * @deprecated
1573          */
1574         public int getSoTimeout() throws SocketException {
1575             if (hasConnection()) {
1576                 return wrappedConnection.getSoTimeout();
1577             } else {
1578                 throw new IllegalStateException("Connection has been released");
1579             }
1580         }
1581 
1582         /***
1583          * @deprecated
1584          */
1585         public String getVirtualHost() {
1586             if (hasConnection()) {
1587                 return wrappedConnection.getVirtualHost();
1588             } else {
1589                 throw new IllegalStateException("Connection has been released");
1590             }
1591         }
1592 
1593         /***
1594          * @deprecated
1595          */
1596         public void setVirtualHost(String host) throws IllegalStateException {
1597             if (hasConnection()) {
1598                 wrappedConnection.setVirtualHost(host);
1599             } else {
1600                 throw new IllegalStateException("Connection has been released");
1601             }
1602         }
1603 
1604         public int getSendBufferSize() throws SocketException {
1605             if (hasConnection()) {
1606                 return wrappedConnection.getSendBufferSize();
1607             } else {
1608                 throw new IllegalStateException("Connection has been released");
1609             }
1610         }
1611 
1612         /***
1613          * @deprecated
1614          */
1615         public void setSendBufferSize(int sendBufferSize) throws SocketException {
1616             if (hasConnection()) {
1617                 wrappedConnection.setSendBufferSize(sendBufferSize);
1618             } else {
1619                 throw new IllegalStateException("Connection has been released");
1620             }
1621         }
1622 
1623         public HttpConnectionParams getParams() {
1624             if (hasConnection()) {
1625                 return wrappedConnection.getParams();
1626             } else {
1627                 throw new IllegalStateException("Connection has been released");
1628             }
1629         }
1630 
1631         public void setParams(final HttpConnectionParams params) {
1632             if (hasConnection()) {
1633                 wrappedConnection.setParams(params);
1634             } else {
1635                 throw new IllegalStateException("Connection has been released");
1636             }
1637         }
1638 
1639         /* (non-Javadoc)
1640          * @see org.apache.commons.httpclient.HttpConnection#print(java.lang.String, java.lang.String)
1641          */
1642         public void print(String data, String charset) throws IOException, IllegalStateException {
1643             if (hasConnection()) {
1644                 wrappedConnection.print(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#printLine(java.lang.String, java.lang.String)
1652          */
1653         public void printLine(String data, String charset)
1654             throws IOException, IllegalStateException {
1655             if (hasConnection()) {
1656                 wrappedConnection.printLine(data, charset);
1657             } else {
1658                 throw new IllegalStateException("Connection has been released");
1659             }
1660         }
1661 
1662         /* (non-Javadoc)
1663          * @see org.apache.commons.httpclient.HttpConnection#setSocketTimeout(int)
1664          */
1665         public void setSocketTimeout(int timeout) throws SocketException, IllegalStateException {
1666             if (hasConnection()) {
1667                 wrappedConnection.setSocketTimeout(timeout);
1668             } else {
1669                 throw new IllegalStateException("Connection has been released");
1670             }
1671         }
1672 
1673     }
1674 
1675 }
1676