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