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.getPort(),
626 conn.getProtocol()
627 );
628 if (conn.getLocalAddress() != null) {
629 connectionConfiguration.setLocalAddress(conn.getLocalAddress());
630 }
631 if (conn.getProxyHost() != null) {
632 connectionConfiguration.setProxy(conn.getProxyHost(), conn.getProxyPort());
633 }
634
635 return connectionConfiguration;
636 }
637
638 /***
639 * Returns {@link HttpConnectionManagerParams parameters} associated
640 * with this connection manager.
641 *
642 * @since 3.0
643 *
644 * @see HttpConnectionManagerParams
645 */
646 public HttpConnectionManagerParams getParams() {
647 return this.params;
648 }
649
650 /***
651 * Assigns {@link HttpConnectionManagerParams parameters} for this
652 * connection manager.
653 *
654 * @since 3.0
655 *
656 * @see HttpConnectionManagerParams
657 */
658 public void setParams(final HttpConnectionManagerParams params) {
659 if (params == null) {
660 throw new IllegalArgumentException("Parameters may not be null");
661 }
662 this.params = params;
663 }
664
665 /***
666 * Global Connection Pool, including per-host pools
667 */
668 private class ConnectionPool {
669
670 /*** The list of free connections */
671 private LinkedList freeConnections = new LinkedList();
672
673 /*** The list of WaitingThreads waiting for a connection */
674 private LinkedList waitingThreads = new LinkedList();
675
676 /***
677 * Map where keys are {@link HostConfiguration}s and values are {@link
678 * HostConnectionPool}s
679 */
680 private final Map mapHosts = new HashMap();
681
682 private IdleConnectionHandler idleConnectionHandler = new IdleConnectionHandler();
683
684 /*** The number of created connections */
685 private int numConnections = 0;
686
687 /***
688 * Cleans up all connection pool resources.
689 */
690 public synchronized void shutdown() {
691
692
693 Iterator iter = freeConnections.iterator();
694 while (iter.hasNext()) {
695 HttpConnection conn = (HttpConnection) iter.next();
696 iter.remove();
697 conn.close();
698 }
699
700
701 shutdownCheckedOutConnections(this);
702
703
704 iter = waitingThreads.iterator();
705 while (iter.hasNext()) {
706 WaitingThread waiter = (WaitingThread) iter.next();
707 iter.remove();
708 waiter.thread.interrupt();
709 }
710
711
712 mapHosts.clear();
713
714
715 idleConnectionHandler.removeAll();
716 }
717
718 /***
719 * Creates a new connection and returns is for use of the calling method.
720 *
721 * @param hostConfiguration the configuration for the connection
722 * @return a new connection or <code>null</code> if none are available
723 */
724 public synchronized HttpConnection createConnection(HostConfiguration hostConfiguration) {
725
726 HttpConnectionWithReference connection = null;
727
728 HostConnectionPool hostPool = getHostPool(hostConfiguration);
729
730 if ((hostPool.numConnections < getMaxConnectionsPerHost())
731 && (numConnections < getMaxTotalConnections())) {
732
733 if (LOG.isDebugEnabled()) {
734 LOG.debug("Allocating new connection, hostConfig=" + hostConfiguration);
735 }
736 connection = new HttpConnectionWithReference(hostConfiguration);
737 connection.getParams().setDefaults(MultiThreadedHttpConnectionManager.this.params);
738 connection.setHttpConnectionManager(MultiThreadedHttpConnectionManager.this);
739 numConnections++;
740 hostPool.numConnections++;
741
742
743
744 storeReferenceToConnection(connection, hostConfiguration, this);
745
746 } else if (LOG.isDebugEnabled()) {
747 if (hostPool.numConnections >= getMaxConnectionsPerHost()) {
748 LOG.debug("No connection allocated, host pool has already reached "
749 + "maxConnectionsPerHost, hostConfig=" + hostConfiguration
750 + ", maxConnectionsPerhost=" + getMaxConnectionsPerHost());
751 } else {
752 LOG.debug("No connection allocated, maxTotalConnections reached, "
753 + "maxTotalConnections=" + getMaxTotalConnections());
754 }
755 }
756
757 return connection;
758 }
759
760 /***
761 * Handles cleaning up for a lost connection with the given config. Decrements any
762 * connection counts and notifies waiting threads, if appropriate.
763 *
764 * @param config the host configuration of the connection that was lost
765 */
766 public synchronized void handleLostConnection(HostConfiguration config) {
767 HostConnectionPool hostPool = getHostPool(config);
768 hostPool.numConnections--;
769
770 numConnections--;
771 notifyWaitingThread(config);
772 }
773
774 /***
775 * Get the pool (list) of connections available for the given hostConfig.
776 *
777 * @param hostConfiguration the configuraton for the connection pool
778 * @return a pool (list) of connections available for the given config
779 */
780 public synchronized HostConnectionPool getHostPool(HostConfiguration hostConfiguration) {
781 LOG.trace("enter HttpConnectionManager.ConnectionPool.getHostPool(HostConfiguration)");
782
783
784 HostConnectionPool listConnections = (HostConnectionPool)
785 mapHosts.get(hostConfiguration);
786 if (listConnections == null) {
787
788 listConnections = new HostConnectionPool();
789 listConnections.hostConfiguration = hostConfiguration;
790 mapHosts.put(hostConfiguration, listConnections);
791 }
792
793 return listConnections;
794 }
795
796 /***
797 * If available, get a free connection for this host
798 *
799 * @param hostConfiguration the configuraton for the connection pool
800 * @return an available connection for the given config
801 */
802 public synchronized HttpConnection getFreeConnection(HostConfiguration hostConfiguration) {
803
804 HttpConnectionWithReference connection = null;
805
806 HostConnectionPool hostPool = getHostPool(hostConfiguration);
807
808 if (hostPool.freeConnections.size() > 0) {
809 connection = (HttpConnectionWithReference) hostPool.freeConnections.removeFirst();
810 freeConnections.remove(connection);
811
812
813 storeReferenceToConnection(connection, hostConfiguration, this);
814 if (LOG.isDebugEnabled()) {
815 LOG.debug("Getting free connection, hostConfig=" + hostConfiguration);
816 }
817
818
819 idleConnectionHandler.remove(connection);
820 } else if (LOG.isDebugEnabled()) {
821 LOG.debug("There were no free connections to get, hostConfig="
822 + hostConfiguration);
823 }
824 return connection;
825 }
826
827 /***
828 * Deletes all closed connections.
829 */
830 public synchronized void deleteClosedConnections() {
831
832 Iterator iter = freeConnections.iterator();
833
834 while (iter.hasNext()) {
835 HttpConnection conn = (HttpConnection) iter.next();
836 if (!conn.isOpen()) {
837 iter.remove();
838 deleteConnection(conn);
839 }
840 }
841 }
842
843 /***
844 * Closes idle connections.
845 * @param idleTimeout
846 */
847 public synchronized void closeIdleConnections(long idleTimeout) {
848 idleConnectionHandler.closeIdleConnections(idleTimeout);
849 }
850
851 /***
852 * Deletes the given connection. This will remove all reference to the connection
853 * so that it can be GCed.
854 *
855 * <p><b>Note:</b> Does not remove the connection from the freeConnections list. It
856 * is assumed that the caller has already handled this case.</p>
857 *
858 * @param connection The connection to delete
859 */
860 private synchronized void deleteConnection(HttpConnection connection) {
861
862 HostConfiguration connectionConfiguration = configurationForConnection(connection);
863
864 if (LOG.isDebugEnabled()) {
865 LOG.debug("Reclaiming connection, hostConfig=" + connectionConfiguration);
866 }
867
868 connection.close();
869
870 HostConnectionPool hostPool = getHostPool(connectionConfiguration);
871
872 hostPool.freeConnections.remove(connection);
873 hostPool.numConnections--;
874 numConnections--;
875
876
877 idleConnectionHandler.remove(connection);
878 }
879
880 /***
881 * Close and delete an old, unused connection to make room for a new one.
882 */
883 public synchronized void deleteLeastUsedConnection() {
884
885 HttpConnection connection = (HttpConnection) freeConnections.removeFirst();
886
887 if (connection != null) {
888 deleteConnection(connection);
889 } else if (LOG.isDebugEnabled()) {
890 LOG.debug("Attempted to reclaim an unused connection but there were none.");
891 }
892 }
893
894 /***
895 * Notifies a waiting thread that a connection for the given configuration is
896 * available.
897 * @param configuration the host config to use for notifying
898 * @see #notifyWaitingThread(HostConnectionPool)
899 */
900 public synchronized void notifyWaitingThread(HostConfiguration configuration) {
901 notifyWaitingThread(getHostPool(configuration));
902 }
903
904 /***
905 * Notifies a waiting thread that a connection for the given configuration is
906 * available. This will wake a thread witing in tis hostPool or if there is not
907 * one a thread in the ConnectionPool will be notified.
908 *
909 * @param hostPool the host pool to use for notifying
910 */
911 public synchronized void notifyWaitingThread(HostConnectionPool hostPool) {
912
913
914
915
916 WaitingThread waitingThread = null;
917
918 if (hostPool.waitingThreads.size() > 0) {
919 if (LOG.isDebugEnabled()) {
920 LOG.debug("Notifying thread waiting on host pool, hostConfig="
921 + hostPool.hostConfiguration);
922 }
923 waitingThread = (WaitingThread) hostPool.waitingThreads.removeFirst();
924 waitingThreads.remove(waitingThread);
925 } else if (waitingThreads.size() > 0) {
926 if (LOG.isDebugEnabled()) {
927 LOG.debug("No-one waiting on host pool, notifying next waiting thread.");
928 }
929 waitingThread = (WaitingThread) waitingThreads.removeFirst();
930 waitingThread.hostConnectionPool.waitingThreads.remove(waitingThread);
931 } else if (LOG.isDebugEnabled()) {
932 LOG.debug("Notifying no-one, there are no waiting threads");
933 }
934
935 if (waitingThread != null) {
936 waitingThread.thread.interrupt();
937 }
938 }
939
940 /***
941 * Marks the given connection as free.
942 * @param conn a connection that is no longer being used
943 */
944 public void freeConnection(HttpConnection conn) {
945
946 HostConfiguration connectionConfiguration = configurationForConnection(conn);
947
948 if (LOG.isDebugEnabled()) {
949 LOG.debug("Freeing connection, hostConfig=" + connectionConfiguration);
950 }
951
952 synchronized (this) {
953
954 if (shutdown) {
955
956
957 conn.close();
958 return;
959 }
960
961 HostConnectionPool hostPool = getHostPool(connectionConfiguration);
962
963
964 hostPool.freeConnections.add(conn);
965 if (hostPool.numConnections == 0) {
966
967 LOG.error("Host connection pool not found, hostConfig="
968 + connectionConfiguration);
969 hostPool.numConnections = 1;
970 }
971
972 freeConnections.add(conn);
973
974
975 removeReferenceToConnection((HttpConnectionWithReference) conn);
976 if (numConnections == 0) {
977
978 LOG.error("Host connection pool not found, hostConfig="
979 + connectionConfiguration);
980 numConnections = 1;
981 }
982
983
984 idleConnectionHandler.add(conn);
985
986 notifyWaitingThread(hostPool);
987 }
988 }
989 }
990
991 /***
992 * A simple struct-like class to combine the objects needed to release a connection's
993 * resources when claimed by the garbage collector.
994 */
995 private static class ConnectionSource {
996
997 /*** The connection pool that created the connection */
998 public ConnectionPool connectionPool;
999
1000 /*** The connection's host configuration */
1001 public HostConfiguration hostConfiguration;
1002 }
1003
1004 /***
1005 * A simple struct-like class to combine the connection list and the count
1006 * of created connections.
1007 */
1008 private static class HostConnectionPool {
1009 /*** The hostConfig this pool is for */
1010 public HostConfiguration hostConfiguration;
1011
1012 /*** The list of free connections */
1013 public LinkedList freeConnections = new LinkedList();
1014
1015 /*** The list of WaitingThreads for this host */
1016 public LinkedList waitingThreads = new LinkedList();
1017
1018 /*** The number of created connections */
1019 public int numConnections = 0;
1020 }
1021
1022 /***
1023 * A simple struct-like class to combine the waiting thread and the connection
1024 * pool it is waiting on.
1025 */
1026 private static class WaitingThread {
1027 /*** The thread that is waiting for a connection */
1028 public Thread thread;
1029
1030 /*** The connection pool the thread is waiting for */
1031 public HostConnectionPool hostConnectionPool;
1032 }
1033
1034 /***
1035 * A thread for listening for HttpConnections reclaimed by the garbage
1036 * collector.
1037 */
1038 private static class ReferenceQueueThread extends Thread {
1039
1040 private boolean shutdown = false;
1041
1042 /***
1043 * Create an instance and make this a daemon thread.
1044 */
1045 public ReferenceQueueThread() {
1046 setDaemon(true);
1047 setName("MultiThreadedHttpConnectionManager cleanup");
1048 }
1049
1050 public void shutdown() {
1051 this.shutdown = true;
1052 }
1053
1054 /***
1055 * Handles cleaning up for the given connection reference.
1056 *
1057 * @param ref the reference to clean up
1058 */
1059 private void handleReference(Reference ref) {
1060
1061 ConnectionSource source = null;
1062
1063 synchronized (REFERENCE_TO_CONNECTION_SOURCE) {
1064 source = (ConnectionSource) REFERENCE_TO_CONNECTION_SOURCE.remove(ref);
1065 }
1066
1067
1068 if (source != null) {
1069 if (LOG.isDebugEnabled()) {
1070 LOG.debug(
1071 "Connection reclaimed by garbage collector, hostConfig="
1072 + source.hostConfiguration);
1073 }
1074
1075 source.connectionPool.handleLostConnection(source.hostConfiguration);
1076 }
1077 }
1078
1079 /***
1080 * Start execution.
1081 */
1082 public void run() {
1083 while (!shutdown) {
1084 try {
1085
1086
1087
1088 Reference ref = REFERENCE_QUEUE.remove(1000);
1089 if (ref != null) {
1090 handleReference(ref);
1091 }
1092 } catch (InterruptedException e) {
1093 LOG.debug("ReferenceQueueThread interrupted", e);
1094 }
1095 }
1096 }
1097
1098 }
1099
1100 /***
1101 * A connection that keeps a reference to itself.
1102 */
1103 private static class HttpConnectionWithReference extends HttpConnection {
1104
1105 public WeakReference reference = new WeakReference(this, REFERENCE_QUEUE);
1106
1107 /***
1108 * @param hostConfiguration
1109 */
1110 public HttpConnectionWithReference(HostConfiguration hostConfiguration) {
1111 super(hostConfiguration);
1112 }
1113
1114 }
1115
1116 /***
1117 * An HttpConnection wrapper that ensures a connection cannot be used
1118 * once released.
1119 */
1120 private static class HttpConnectionAdapter extends HttpConnection {
1121
1122
1123 private HttpConnection wrappedConnection;
1124
1125 /***
1126 * Creates a new HttpConnectionAdapter.
1127 * @param connection the connection to be wrapped
1128 */
1129 public HttpConnectionAdapter(HttpConnection connection) {
1130 super(connection.getHost(), connection.getPort(), connection.getProtocol());
1131 this.wrappedConnection = connection;
1132 }
1133
1134 /***
1135 * Tests if the wrapped connection is still available.
1136 * @return boolean
1137 */
1138 protected boolean hasConnection() {
1139 return wrappedConnection != null;
1140 }
1141
1142 /***
1143 * @return HttpConnection
1144 */
1145 HttpConnection getWrappedConnection() {
1146 return wrappedConnection;
1147 }
1148
1149 public void close() {
1150 if (hasConnection()) {
1151 wrappedConnection.close();
1152 } else {
1153
1154 }
1155 }
1156
1157 public InetAddress getLocalAddress() {
1158 if (hasConnection()) {
1159 return wrappedConnection.getLocalAddress();
1160 } else {
1161 return null;
1162 }
1163 }
1164
1165 /***
1166 * @deprecated
1167 */
1168 public boolean isStaleCheckingEnabled() {
1169 if (hasConnection()) {
1170 return wrappedConnection.isStaleCheckingEnabled();
1171 } else {
1172 return false;
1173 }
1174 }
1175
1176 public void setLocalAddress(InetAddress localAddress) {
1177 if (hasConnection()) {
1178 wrappedConnection.setLocalAddress(localAddress);
1179 } else {
1180 throw new IllegalStateException("Connection has been released");
1181 }
1182 }
1183
1184 /***
1185 * @deprecated
1186 */
1187 public void setStaleCheckingEnabled(boolean staleCheckEnabled) {
1188 if (hasConnection()) {
1189 wrappedConnection.setStaleCheckingEnabled(staleCheckEnabled);
1190 } else {
1191 throw new IllegalStateException("Connection has been released");
1192 }
1193 }
1194
1195 public String getHost() {
1196 if (hasConnection()) {
1197 return wrappedConnection.getHost();
1198 } else {
1199 return null;
1200 }
1201 }
1202
1203 public HttpConnectionManager getHttpConnectionManager() {
1204 if (hasConnection()) {
1205 return wrappedConnection.getHttpConnectionManager();
1206 } else {
1207 return null;
1208 }
1209 }
1210
1211 public InputStream getLastResponseInputStream() {
1212 if (hasConnection()) {
1213 return wrappedConnection.getLastResponseInputStream();
1214 } else {
1215 return null;
1216 }
1217 }
1218
1219 public int getPort() {
1220 if (hasConnection()) {
1221 return wrappedConnection.getPort();
1222 } else {
1223 return -1;
1224 }
1225 }
1226
1227 public Protocol getProtocol() {
1228 if (hasConnection()) {
1229 return wrappedConnection.getProtocol();
1230 } else {
1231 return null;
1232 }
1233 }
1234
1235 public String getProxyHost() {
1236 if (hasConnection()) {
1237 return wrappedConnection.getProxyHost();
1238 } else {
1239 return null;
1240 }
1241 }
1242
1243 public int getProxyPort() {
1244 if (hasConnection()) {
1245 return wrappedConnection.getProxyPort();
1246 } else {
1247 return -1;
1248 }
1249 }
1250
1251 public OutputStream getRequestOutputStream()
1252 throws IOException, IllegalStateException {
1253 if (hasConnection()) {
1254 return wrappedConnection.getRequestOutputStream();
1255 } else {
1256 return null;
1257 }
1258 }
1259
1260 public InputStream getResponseInputStream()
1261 throws IOException, IllegalStateException {
1262 if (hasConnection()) {
1263 return wrappedConnection.getResponseInputStream();
1264 } else {
1265 return null;
1266 }
1267 }
1268
1269 public boolean isOpen() {
1270 if (hasConnection()) {
1271 return wrappedConnection.isOpen();
1272 } else {
1273 return false;
1274 }
1275 }
1276
1277 public boolean closeIfStale() throws IOException {
1278 if (hasConnection()) {
1279 return wrappedConnection.closeIfStale();
1280 } else {
1281 return false;
1282 }
1283 }
1284
1285 public boolean isProxied() {
1286 if (hasConnection()) {
1287 return wrappedConnection.isProxied();
1288 } else {
1289 return false;
1290 }
1291 }
1292
1293 public boolean isResponseAvailable() throws IOException {
1294 if (hasConnection()) {
1295 return wrappedConnection.isResponseAvailable();
1296 } else {
1297 return false;
1298 }
1299 }
1300
1301 public boolean isResponseAvailable(int timeout) throws IOException {
1302 if (hasConnection()) {
1303 return wrappedConnection.isResponseAvailable(timeout);
1304 } else {
1305 return false;
1306 }
1307 }
1308
1309 public boolean isSecure() {
1310 if (hasConnection()) {
1311 return wrappedConnection.isSecure();
1312 } else {
1313 return false;
1314 }
1315 }
1316
1317 public boolean isTransparent() {
1318 if (hasConnection()) {
1319 return wrappedConnection.isTransparent();
1320 } else {
1321 return false;
1322 }
1323 }
1324
1325 public void open() throws IOException {
1326 if (hasConnection()) {
1327 wrappedConnection.open();
1328 } else {
1329 throw new IllegalStateException("Connection has been released");
1330 }
1331 }
1332
1333 /***
1334 * @deprecated
1335 */
1336 public void print(String data)
1337 throws IOException, IllegalStateException {
1338 if (hasConnection()) {
1339 wrappedConnection.print(data);
1340 } else {
1341 throw new IllegalStateException("Connection has been released");
1342 }
1343 }
1344
1345 public void printLine()
1346 throws IOException, IllegalStateException {
1347 if (hasConnection()) {
1348 wrappedConnection.printLine();
1349 } else {
1350 throw new IllegalStateException("Connection has been released");
1351 }
1352 }
1353
1354 /***
1355 * @deprecated
1356 */
1357 public void printLine(String data)
1358 throws IOException, IllegalStateException {
1359 if (hasConnection()) {
1360 wrappedConnection.printLine(data);
1361 } else {
1362 throw new IllegalStateException("Connection has been released");
1363 }
1364 }
1365
1366 /***
1367 * @deprecated
1368 */
1369 public String readLine() throws IOException, IllegalStateException {
1370 if (hasConnection()) {
1371 return wrappedConnection.readLine();
1372 } else {
1373 throw new IllegalStateException("Connection has been released");
1374 }
1375 }
1376
1377 public String readLine(String charset) throws IOException, IllegalStateException {
1378 if (hasConnection()) {
1379 return wrappedConnection.readLine(charset);
1380 } else {
1381 throw new IllegalStateException("Connection has been released");
1382 }
1383 }
1384
1385 public void releaseConnection() {
1386 if (!isLocked() && hasConnection()) {
1387 HttpConnection wrappedConnection = this.wrappedConnection;
1388 this.wrappedConnection = null;
1389 wrappedConnection.releaseConnection();
1390 } else {
1391
1392 }
1393 }
1394
1395 /***
1396 * @deprecated
1397 */
1398 public void setConnectionTimeout(int timeout) {
1399 if (hasConnection()) {
1400 wrappedConnection.setConnectionTimeout(timeout);
1401 } else {
1402
1403 }
1404 }
1405
1406 public void setHost(String host) throws IllegalStateException {
1407 if (hasConnection()) {
1408 wrappedConnection.setHost(host);
1409 } else {
1410
1411 }
1412 }
1413
1414 public void setHttpConnectionManager(HttpConnectionManager httpConnectionManager) {
1415 if (hasConnection()) {
1416 wrappedConnection.setHttpConnectionManager(httpConnectionManager);
1417 } else {
1418
1419 }
1420 }
1421
1422 public void setLastResponseInputStream(InputStream inStream) {
1423 if (hasConnection()) {
1424 wrappedConnection.setLastResponseInputStream(inStream);
1425 } else {
1426
1427 }
1428 }
1429
1430 public void setPort(int port) throws IllegalStateException {
1431 if (hasConnection()) {
1432 wrappedConnection.setPort(port);
1433 } else {
1434
1435 }
1436 }
1437
1438 public void setProtocol(Protocol protocol) {
1439 if (hasConnection()) {
1440 wrappedConnection.setProtocol(protocol);
1441 } else {
1442
1443 }
1444 }
1445
1446 public void setProxyHost(String host) throws IllegalStateException {
1447 if (hasConnection()) {
1448 wrappedConnection.setProxyHost(host);
1449 } else {
1450
1451 }
1452 }
1453
1454 public void setProxyPort(int port) throws IllegalStateException {
1455 if (hasConnection()) {
1456 wrappedConnection.setProxyPort(port);
1457 } else {
1458
1459 }
1460 }
1461
1462 /***
1463 * @deprecated
1464 */
1465 public void setSoTimeout(int timeout)
1466 throws SocketException, IllegalStateException {
1467 if (hasConnection()) {
1468 wrappedConnection.setSoTimeout(timeout);
1469 } else {
1470
1471 }
1472 }
1473
1474 /***
1475 * @deprecated
1476 */
1477 public void shutdownOutput() {
1478 if (hasConnection()) {
1479 wrappedConnection.shutdownOutput();
1480 } else {
1481
1482 }
1483 }
1484
1485 public void tunnelCreated() throws IllegalStateException, IOException {
1486 if (hasConnection()) {
1487 wrappedConnection.tunnelCreated();
1488 } else {
1489
1490 }
1491 }
1492
1493 public void write(byte[] data, int offset, int length)
1494 throws IOException, IllegalStateException {
1495 if (hasConnection()) {
1496 wrappedConnection.write(data, offset, length);
1497 } else {
1498 throw new IllegalStateException("Connection has been released");
1499 }
1500 }
1501
1502 public void write(byte[] data)
1503 throws IOException, IllegalStateException {
1504 if (hasConnection()) {
1505 wrappedConnection.write(data);
1506 } else {
1507 throw new IllegalStateException("Connection has been released");
1508 }
1509 }
1510
1511 public void writeLine()
1512 throws IOException, IllegalStateException {
1513 if (hasConnection()) {
1514 wrappedConnection.writeLine();
1515 } else {
1516 throw new IllegalStateException("Connection has been released");
1517 }
1518 }
1519
1520 public void writeLine(byte[] data)
1521 throws IOException, IllegalStateException {
1522 if (hasConnection()) {
1523 wrappedConnection.writeLine(data);
1524 } else {
1525 throw new IllegalStateException("Connection has been released");
1526 }
1527 }
1528
1529 public void flushRequestOutputStream() throws IOException {
1530 if (hasConnection()) {
1531 wrappedConnection.flushRequestOutputStream();
1532 } else {
1533 throw new IllegalStateException("Connection has been released");
1534 }
1535 }
1536
1537 /***
1538 * @deprecated
1539 */
1540 public int getSoTimeout() throws SocketException {
1541 if (hasConnection()) {
1542 return wrappedConnection.getSoTimeout();
1543 } else {
1544 throw new IllegalStateException("Connection has been released");
1545 }
1546 }
1547
1548 /***
1549 * @deprecated
1550 */
1551 public String getVirtualHost() {
1552 if (hasConnection()) {
1553 return wrappedConnection.getVirtualHost();
1554 } else {
1555 throw new IllegalStateException("Connection has been released");
1556 }
1557 }
1558
1559 /***
1560 * @deprecated
1561 */
1562 public void setVirtualHost(String host) throws IllegalStateException {
1563 if (hasConnection()) {
1564 wrappedConnection.setVirtualHost(host);
1565 } else {
1566 throw new IllegalStateException("Connection has been released");
1567 }
1568 }
1569
1570 public int getSendBufferSize() throws SocketException {
1571 if (hasConnection()) {
1572 return wrappedConnection.getSendBufferSize();
1573 } else {
1574 throw new IllegalStateException("Connection has been released");
1575 }
1576 }
1577
1578 /***
1579 * @deprecated
1580 */
1581 public void setSendBufferSize(int sendBufferSize) throws SocketException {
1582 if (hasConnection()) {
1583 wrappedConnection.setSendBufferSize(sendBufferSize);
1584 } else {
1585 throw new IllegalStateException("Connection has been released");
1586 }
1587 }
1588
1589 public HttpConnectionParams getParams() {
1590 if (hasConnection()) {
1591 return wrappedConnection.getParams();
1592 } else {
1593 throw new IllegalStateException("Connection has been released");
1594 }
1595 }
1596
1597 public void setParams(final HttpConnectionParams params) {
1598 if (hasConnection()) {
1599 wrappedConnection.setParams(params);
1600 } else {
1601 throw new IllegalStateException("Connection has been released");
1602 }
1603 }
1604
1605
1606
1607
1608 public void print(String data, String charset) throws IOException, IllegalStateException {
1609 if (hasConnection()) {
1610 wrappedConnection.print(data, charset);
1611 } else {
1612 throw new IllegalStateException("Connection has been released");
1613 }
1614 }
1615
1616
1617
1618
1619 public void printLine(String data, String charset)
1620 throws IOException, IllegalStateException {
1621 if (hasConnection()) {
1622 wrappedConnection.printLine(data, charset);
1623 } else {
1624 throw new IllegalStateException("Connection has been released");
1625 }
1626 }
1627
1628
1629
1630
1631 public void setSocketTimeout(int timeout) throws SocketException, IllegalStateException {
1632 if (hasConnection()) {
1633 wrappedConnection.setSocketTimeout(timeout);
1634 } else {
1635 throw new IllegalStateException("Connection has been released");
1636 }
1637 }
1638
1639 }
1640
1641 }
1642