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