001: /**
002: * ====================================================================
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: * ====================================================================
016: *
017: */package org.archive.httpclient;
018:
019: import java.io.IOException;
020: import java.io.InputStream;
021: import java.util.ArrayList;
022: import java.util.Iterator;
023: import java.util.List;
024: import java.util.logging.Level;
025: import java.util.logging.Logger;
026:
027: import org.apache.commons.httpclient.HostConfiguration;
028: import org.apache.commons.httpclient.HttpConnection;
029: import org.apache.commons.httpclient.HttpConnectionManager;
030: import org.apache.commons.httpclient.params.HttpConnectionManagerParams;
031:
032: /**
033: * A simple, but thread-safe HttpClient {@link HttpConnectionManager}.
034: * Based on {@link org.apache.commons.httpclient.SimpleHttpConnectionManager}.
035: *
036: * <b>Java >= 1.4 is recommended.</b>
037: *
038: * @author Christian Kohlschuetter
039: */
040: public final class ThreadLocalHttpConnectionManager implements
041: HttpConnectionManager {
042:
043: private static final CloserThread closer = new CloserThread();
044: private static final Logger logger = Logger
045: .getLogger(ThreadLocalHttpConnectionManager.class.getName());
046:
047: private final ThreadLocal tl = new ThreadLocal() {
048: protected synchronized Object initialValue() {
049: return new ConnectionInfo();
050: }
051: };
052:
053: private ConnectionInfo getConnectionInfo() {
054: return (ConnectionInfo) tl.get();
055: }
056:
057: private final class ConnectionInfo {
058: /** The http connection */
059: private HttpConnection conn = null;
060:
061: /**
062: * The time the connection was made idle.
063: */
064: private long idleStartTime = Long.MAX_VALUE;
065: }
066:
067: public ThreadLocalHttpConnectionManager() {
068: }
069:
070: /**
071: * Since the same connection is about to be reused, make sure the
072: * previous request was completely processed, and if not
073: * consume it now.
074: * @param conn The connection
075: * @return true, if the connection is reusable
076: */
077: private static boolean finishLastResponse(final HttpConnection conn) {
078: InputStream lastResponse = conn.getLastResponseInputStream();
079: if (lastResponse != null) {
080: conn.setLastResponseInputStream(null);
081: try {
082: lastResponse.close();
083: return true;
084: } catch (IOException ioe) {
085: // force reconnect.
086: return false;
087: }
088: } else {
089: return false;
090: }
091: }
092:
093: /**
094: * Collection of parameters associated with this connection manager.
095: */
096: private HttpConnectionManagerParams params = new HttpConnectionManagerParams();
097:
098: /**
099: * @see HttpConnectionManager#getConnection(HostConfiguration)
100: */
101: public HttpConnection getConnection(
102: final HostConfiguration hostConfiguration) {
103: return getConnection(hostConfiguration, 0);
104: }
105:
106: /**
107: * Gets the staleCheckingEnabled value to be set on HttpConnections that are created.
108: *
109: * @return <code>true</code> if stale checking will be enabled on HttpConections
110: *
111: * @see HttpConnection#isStaleCheckingEnabled()
112: *
113: * @deprecated Use {@link HttpConnectionManagerParams#isStaleCheckingEnabled()},
114: * {@link HttpConnectionManager#getParams()}.
115: */
116: public boolean isConnectionStaleCheckingEnabled() {
117: return this .params.isStaleCheckingEnabled();
118: }
119:
120: /**
121: * Sets the staleCheckingEnabled value to be set on HttpConnections that are created.
122: *
123: * @param connectionStaleCheckingEnabled <code>true</code> if stale checking will be enabled
124: * on HttpConections
125: *
126: * @see HttpConnection#setStaleCheckingEnabled(boolean)
127: *
128: * @deprecated Use {@link HttpConnectionManagerParams#setStaleCheckingEnabled(boolean)},
129: * {@link HttpConnectionManager#getParams()}.
130: */
131: public void setConnectionStaleCheckingEnabled(
132: final boolean connectionStaleCheckingEnabled) {
133: this .params
134: .setStaleCheckingEnabled(connectionStaleCheckingEnabled);
135: }
136:
137: /**
138: * @see HttpConnectionManager#getConnectionWithTimeout(HostConfiguration, long)
139: *
140: * @since 3.0
141: */
142: public HttpConnection getConnectionWithTimeout(
143: final HostConfiguration hostConfiguration,
144: final long timeout) {
145:
146: final ConnectionInfo ci = getConnectionInfo();
147: HttpConnection httpConnection = ci.conn;
148:
149: // make sure the host and proxy are correct for this connection
150: // close it and set the values if they are not
151: if (httpConnection == null
152: || !finishLastResponse(httpConnection)
153: || !hostConfiguration.hostEquals(httpConnection)
154: || !hostConfiguration.proxyEquals(httpConnection)) {
155:
156: if (httpConnection != null && httpConnection.isOpen()) {
157: closer.closeConnection(httpConnection);
158: }
159:
160: httpConnection = new HttpConnection(hostConfiguration);
161: httpConnection.setHttpConnectionManager(this );
162: httpConnection.getParams().setDefaults(this .params);
163: ci.conn = httpConnection;
164:
165: httpConnection.setHost(hostConfiguration.getHost());
166: httpConnection.setPort(hostConfiguration.getPort());
167: httpConnection.setProtocol(hostConfiguration.getProtocol());
168: httpConnection.setLocalAddress(hostConfiguration
169: .getLocalAddress());
170:
171: httpConnection.setProxyHost(hostConfiguration
172: .getProxyHost());
173: httpConnection.setProxyPort(hostConfiguration
174: .getProxyPort());
175: }
176:
177: // remove the connection from the timeout handler
178: ci.idleStartTime = Long.MAX_VALUE;
179:
180: return httpConnection;
181: }
182:
183: /**
184: * @see HttpConnectionManager#getConnection(HostConfiguration, long)
185: *
186: * @deprecated Use #getConnectionWithTimeout(HostConfiguration, long)
187: */
188: public HttpConnection getConnection(
189: final HostConfiguration hostConfiguration,
190: final long timeout) {
191: return getConnectionWithTimeout(hostConfiguration, timeout);
192: }
193:
194: /**
195: * @see HttpConnectionManager#releaseConnection(org.apache.commons.httpclient.HttpConnection)
196: */
197: public void releaseConnection(final HttpConnection conn) {
198: final ConnectionInfo ci = getConnectionInfo();
199: HttpConnection httpConnection = ci.conn;
200:
201: if (conn != httpConnection) {
202: throw new IllegalStateException(
203: "Unexpected release of an unknown connection.");
204: }
205:
206: finishLastResponse(httpConnection);
207:
208: // track the time the connection was made idle
209: ci.idleStartTime = System.currentTimeMillis();
210: }
211:
212: /**
213: * Returns {@link HttpConnectionManagerParams parameters} associated
214: * with this connection manager.
215: *
216: * @since 2.1
217: *
218: * @see HttpConnectionManagerParams
219: */
220: public HttpConnectionManagerParams getParams() {
221: return this .params;
222: }
223:
224: /**
225: * Assigns {@link HttpConnectionManagerParams parameters} for this
226: * connection manager.
227: *
228: * @since 2.1
229: *
230: * @see HttpConnectionManagerParams
231: */
232: public void setParams(final HttpConnectionManagerParams p) {
233: if (p == null) {
234: throw new IllegalArgumentException(
235: "Parameters may not be null");
236: }
237: this .params = p;
238: }
239:
240: /**
241: * @since 3.0
242: */
243: public void closeIdleConnections(final long idleTimeout) {
244: long maxIdleTime = System.currentTimeMillis() - idleTimeout;
245:
246: final ConnectionInfo ci = getConnectionInfo();
247:
248: if (ci.idleStartTime <= maxIdleTime) {
249: ci.conn.close();
250: }
251: }
252:
253: private static final class CloserThread extends Thread {
254: private List<HttpConnection> connections = new ArrayList<HttpConnection>();
255:
256: private static final int SLEEP_INTERVAL = 5000;
257:
258: public CloserThread() {
259: super ("HttpConnection closer");
260: // Make this a daemon thread so it can't be responsible for the JVM
261: // not shutting down.
262: setDaemon(true);
263: start();
264: }
265:
266: public void closeConnection(final HttpConnection conn) {
267: synchronized (connections) {
268: connections.add(conn);
269: }
270: }
271:
272: public void run() {
273: try {
274: while (!Thread.interrupted()) {
275: Thread.sleep(SLEEP_INTERVAL);
276:
277: List<HttpConnection> s;
278: synchronized (connections) {
279: s = connections;
280: connections = new ArrayList<HttpConnection>();
281: }
282: logger.log(Level.INFO, "Closing " + s.size()
283: + " HttpConnections");
284: for (final Iterator<HttpConnection> it = s
285: .iterator(); it.hasNext();) {
286: HttpConnection conn = it.next();
287: conn.close();
288: conn.setHttpConnectionManager(null);
289: it.remove();
290: }
291: }
292: } catch (InterruptedException e) {
293: return;
294: }
295: }
296: }
297: }
|