1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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
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;
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
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
115 synchronized (ALL_CONNECTION_MANAGERS) {
116
117
118 MultiThreadedHttpConnectionManager[]
119 connManagers = (MultiThreadedHttpConnectionManager[])
120 ALL_CONNECTION_MANAGERS.keySet().toArray(
121 new MultiThreadedHttpConnectionManager
122 [ALL_CONNECTION_MANAGERS.size()]
123 );
124
125
126
127 for (int i=0; i<connManagers.length; i++) {
128 if (connManagers[i] != null)
129 connManagers[i].shutdown();
130 }
131 }
132
133
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
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
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
211
212 for (Iterator i = connectionsToClose.iterator(); i.hasNext();) {
213 HttpConnection connection = (HttpConnection) i.next();
214 connection.close();
215
216
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
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
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
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
374
375
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
419
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
450
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
467
468 if (hostPool.freeConnections.size() > 0) {
469 connection = connectionPool.getFreeConnection(hostConfiguration);
470
471
472
473 } else if ((hostPool.numConnections < maxHostConnections)
474 && (connectionPool.numConnections < maxTotalConnections)) {
475
476 connection = connectionPool.createConnection(hostConfiguration);
477
478
479
480
481 } else if ((hostPool.numConnections < maxHostConnections)
482 && (connectionPool.freeConnections.size() > 0)) {
483
484 connectionPool.deleteLeastUsedConnection();
485 connection = connectionPool.createConnection(hostConfiguration);
486
487
488
489
490 } else {
491
492
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
526
527
528 } finally {
529 if (!waitingThread.interruptedByConnectionPool) {
530
531
532
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
634 conn = ((HttpConnectionAdapter) conn).getWrappedConnection();
635 } else {
636
637
638 }
639
640
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
725 Iterator iter = freeConnections.iterator();
726 while (iter.hasNext()) {
727 HttpConnection conn = (HttpConnection) iter.next();
728 iter.remove();
729 conn.close();
730 }
731
732
733 shutdownCheckedOutConnections(this);
734
735
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
745 mapHosts.clear();
746
747
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
770
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
800 HostConnectionPool listConnections = (HostConnectionPool)
801 mapHosts.get(hostConfiguration);
802 if (listConnections == null) {
803
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
828
829 storeReferenceToConnection(connection, hostConfiguration, this);
830 if (LOG.isDebugEnabled()) {
831 LOG.debug("Getting free connection, hostConfig=" + hostConfiguration);
832 }
833
834
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
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
931
932
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
974
975 conn.close();
976 return;
977 }
978
979 HostConnectionPool hostPool = getHostPool(connectionConfiguration);
980
981
982 hostPool.freeConnections.add(conn);
983 if (hostPool.numConnections == 0) {
984
985 LOG.error("Host connection pool not found, hostConfig="
986 + connectionConfiguration);
987 hostPool.numConnections = 1;
988 }
989
990 freeConnections.add(conn);
991
992
993 removeReferenceToConnection((HttpConnectionWithReference) conn);
994 if (numConnections == 0) {
995
996 LOG.error("Host connection pool not found, hostConfig="
997 + connectionConfiguration);
998 numConnections = 1;
999 }
1000
1001
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
1091
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
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
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
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
1414 }
1415 }
1416
1417 /***
1418 * @deprecated
1419 */
1420 public void setConnectionTimeout(int timeout) {
1421 if (hasConnection()) {
1422 wrappedConnection.setConnectionTimeout(timeout);
1423 } else {
1424
1425 }
1426 }
1427
1428 public void setHost(String host) throws IllegalStateException {
1429 if (hasConnection()) {
1430 wrappedConnection.setHost(host);
1431 } else {
1432
1433 }
1434 }
1435
1436 public void setHttpConnectionManager(HttpConnectionManager httpConnectionManager) {
1437 if (hasConnection()) {
1438 wrappedConnection.setHttpConnectionManager(httpConnectionManager);
1439 } else {
1440
1441 }
1442 }
1443
1444 public void setLastResponseInputStream(InputStream inStream) {
1445 if (hasConnection()) {
1446 wrappedConnection.setLastResponseInputStream(inStream);
1447 } else {
1448
1449 }
1450 }
1451
1452 public void setPort(int port) throws IllegalStateException {
1453 if (hasConnection()) {
1454 wrappedConnection.setPort(port);
1455 } else {
1456
1457 }
1458 }
1459
1460 public void setProtocol(Protocol protocol) {
1461 if (hasConnection()) {
1462 wrappedConnection.setProtocol(protocol);
1463 } else {
1464
1465 }
1466 }
1467
1468 public void setProxyHost(String host) throws IllegalStateException {
1469 if (hasConnection()) {
1470 wrappedConnection.setProxyHost(host);
1471 } else {
1472
1473 }
1474 }
1475
1476 public void setProxyPort(int port) throws IllegalStateException {
1477 if (hasConnection()) {
1478 wrappedConnection.setProxyPort(port);
1479 } else {
1480
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
1493 }
1494 }
1495
1496 /***
1497 * @deprecated
1498 */
1499 public void shutdownOutput() {
1500 if (hasConnection()) {
1501 wrappedConnection.shutdownOutput();
1502 } else {
1503
1504 }
1505 }
1506
1507 public void tunnelCreated() throws IllegalStateException, IOException {
1508 if (hasConnection()) {
1509 wrappedConnection.tunnelCreated();
1510 } else {
1511
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
1628
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
1639
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
1651
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