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