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