1 /* 2 * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/SingleClientConnManager.java $ 3 * $Revision: 673450 $ 4 * $Date: 2008-07-02 10:35:05 -0700 (Wed, 02 Jul 2008) $ 5 * 6 * ==================================================================== 7 * Licensed to the Apache Software Foundation (ASF) under one 8 * or more contributor license agreements. See the NOTICE file 9 * distributed with this work for additional information 10 * regarding copyright ownership. The ASF licenses this file 11 * to you under the Apache License, Version 2.0 (the 12 * "License"); you may not use this file except in compliance 13 * with the License. You may obtain a copy of the License at 14 * 15 * http://www.apache.org/licenses/LICENSE-2.0 16 * 17 * Unless required by applicable law or agreed to in writing, 18 * software distributed under the License is distributed on an 19 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 20 * KIND, either express or implied. See the License for the 21 * specific language governing permissions and limitations 22 * under the License. 23 * ==================================================================== 24 * 25 * This software consists of voluntary contributions made by many 26 * individuals on behalf of the Apache Software Foundation. For more 27 * information on the Apache Software Foundation, please see 28 * <http://www.apache.org/>. 29 * 30 */ 31 32 package org.apache.http.impl.conn; 33 34 import dalvik.system.SocketTagger; 35 import java.io.IOException; 36 import java.net.Socket; 37 import java.util.concurrent.TimeUnit; 38 39 import org.apache.commons.logging.Log; 40 import org.apache.commons.logging.LogFactory; 41 import org.apache.http.conn.ClientConnectionManager; 42 import org.apache.http.conn.ClientConnectionOperator; 43 import org.apache.http.conn.ClientConnectionRequest; 44 import org.apache.http.conn.ManagedClientConnection; 45 import org.apache.http.conn.routing.HttpRoute; 46 import org.apache.http.conn.routing.RouteTracker; 47 import org.apache.http.conn.scheme.SchemeRegistry; 48 import org.apache.http.params.HttpParams; 49 50 51 /** 52 * A connection "manager" for a single connection. 53 * This manager is good only for single-threaded use. 54 * Allocation <i>always</i> returns the connection immediately, 55 * even if it has not been released after the previous allocation. 56 * In that case, a {@link #MISUSE_MESSAGE warning} is logged 57 * and the previously issued connection is revoked. 58 * <p> 59 * This class is derived from <code>SimpleHttpConnectionManager</code> 60 * in HttpClient 3. See there for original authors. 61 * </p> 62 * 63 * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> 64 * @author <a href="mailto:becke@u.washington.edu">Michael Becke</a> 65 * 66 * 67 * <!-- empty lines to avoid svn diff problems --> 68 * @version $Revision: 673450 $ 69 * 70 * @since 4.0 71 */ 72 public class SingleClientConnManager implements ClientConnectionManager { 73 74 private final Log log = LogFactory.getLog(getClass()); 75 76 /** The message to be logged on multiple allocation. */ 77 public final static String MISUSE_MESSAGE = 78 "Invalid use of SingleClientConnManager: connection still allocated.\n" + 79 "Make sure to release the connection before allocating another one."; 80 81 82 /** The schemes supported by this connection manager. */ 83 protected SchemeRegistry schemeRegistry; 84 85 /** The operator for opening and updating connections. */ 86 protected ClientConnectionOperator connOperator; 87 88 /** The one and only entry in this pool. */ 89 protected PoolEntry uniquePoolEntry; 90 91 /** The currently issued managed connection, if any. */ 92 protected ConnAdapter managedConn; 93 94 /** The time of the last connection release, or -1. */ 95 protected long lastReleaseTime; 96 97 /** The time the last released connection expires and shouldn't be reused. */ 98 protected long connectionExpiresTime; 99 100 /** Whether the connection should be shut down on release. */ 101 protected boolean alwaysShutDown; 102 103 /** Indicates whether this connection manager is shut down. */ 104 protected volatile boolean isShutDown; 105 106 107 108 109 /** 110 * Creates a new simple connection manager. 111 * 112 * @param params the parameters for this manager 113 * @param schreg the scheme registry 114 */ SingleClientConnManager(HttpParams params, SchemeRegistry schreg)115 public SingleClientConnManager(HttpParams params, 116 SchemeRegistry schreg) { 117 118 if (schreg == null) { 119 throw new IllegalArgumentException 120 ("Scheme registry must not be null."); 121 } 122 this.schemeRegistry = schreg; 123 this.connOperator = createConnectionOperator(schreg); 124 this.uniquePoolEntry = new PoolEntry(); 125 this.managedConn = null; 126 this.lastReleaseTime = -1L; 127 this.alwaysShutDown = false; //@@@ from params? as argument? 128 this.isShutDown = false; 129 130 } // <constructor> 131 132 133 @Override finalize()134 protected void finalize() throws Throwable { 135 shutdown(); 136 super.finalize(); 137 } 138 139 140 // non-javadoc, see interface ClientConnectionManager getSchemeRegistry()141 public SchemeRegistry getSchemeRegistry() { 142 return this.schemeRegistry; 143 } 144 145 146 /** 147 * Hook for creating the connection operator. 148 * It is called by the constructor. 149 * Derived classes can override this method to change the 150 * instantiation of the operator. 151 * The default implementation here instantiates 152 * {@link DefaultClientConnectionOperator DefaultClientConnectionOperator}. 153 * 154 * @param schreg the scheme registry to use, or <code>null</code> 155 * 156 * @return the connection operator to use 157 */ 158 protected ClientConnectionOperator createConnectionOperator(SchemeRegistry schreg)159 createConnectionOperator(SchemeRegistry schreg) { 160 161 return new DefaultClientConnectionOperator(schreg); 162 } 163 164 165 /** 166 * Asserts that this manager is not shut down. 167 * 168 * @throws IllegalStateException if this manager is shut down 169 */ assertStillUp()170 protected final void assertStillUp() 171 throws IllegalStateException { 172 173 if (this.isShutDown) 174 throw new IllegalStateException("Manager is shut down."); 175 } 176 177 requestConnection( final HttpRoute route, final Object state)178 public final ClientConnectionRequest requestConnection( 179 final HttpRoute route, 180 final Object state) { 181 182 return new ClientConnectionRequest() { 183 184 public void abortRequest() { 185 // Nothing to abort, since requests are immediate. 186 } 187 188 public ManagedClientConnection getConnection( 189 long timeout, TimeUnit tunit) { 190 return SingleClientConnManager.this.getConnection( 191 route, state); 192 } 193 194 }; 195 } 196 197 198 /** 199 * Obtains a connection. 200 * This method does not block. 201 * 202 * @param route where the connection should point to 203 * 204 * @return a connection that can be used to communicate 205 * along the given route 206 */ 207 public ManagedClientConnection getConnection(HttpRoute route, Object state) { 208 209 if (route == null) { 210 throw new IllegalArgumentException("Route may not be null."); 211 } 212 assertStillUp(); 213 214 if (log.isDebugEnabled()) { 215 log.debug("Get connection for route " + route); 216 } 217 218 if (managedConn != null) 219 revokeConnection(); 220 221 // check re-usability of the connection 222 boolean recreate = false; 223 boolean shutdown = false; 224 225 // Kill the connection if it expired. 226 closeExpiredConnections(); 227 228 if (uniquePoolEntry.connection.isOpen()) { 229 RouteTracker tracker = uniquePoolEntry.tracker; 230 shutdown = (tracker == null || // can happen if method is aborted 231 !tracker.toRoute().equals(route)); 232 } else { 233 // If the connection is not open, create a new PoolEntry, 234 // as the connection may have been marked not reusable, 235 // due to aborts -- and the PoolEntry should not be reused 236 // either. There's no harm in recreating an entry if 237 // the connection is closed. 238 recreate = true; 239 } 240 241 if (shutdown) { 242 recreate = true; 243 try { 244 uniquePoolEntry.shutdown(); 245 } catch (IOException iox) { 246 log.debug("Problem shutting down connection.", iox); 247 } 248 } 249 250 if (recreate) 251 uniquePoolEntry = new PoolEntry(); 252 253 // BEGIN android-changed 254 // When using a recycled Socket, we need to re-tag it with any 255 // updated statistics options. 256 try { 257 final Socket socket = uniquePoolEntry.connection.getSocket(); 258 if (socket != null) { 259 SocketTagger.get().tag(socket); 260 } 261 } catch (IOException iox) { 262 log.debug("Problem tagging socket.", iox); 263 } 264 // END android-changed 265 266 managedConn = new ConnAdapter(uniquePoolEntry, route); 267 268 return managedConn; 269 } 270 271 272 // non-javadoc, see interface ClientConnectionManager 273 public void releaseConnection(ManagedClientConnection conn, long validDuration, TimeUnit timeUnit) { 274 assertStillUp(); 275 276 if (!(conn instanceof ConnAdapter)) { 277 throw new IllegalArgumentException 278 ("Connection class mismatch, " + 279 "connection not obtained from this manager."); 280 } 281 282 if (log.isDebugEnabled()) { 283 log.debug("Releasing connection " + conn); 284 } 285 286 ConnAdapter sca = (ConnAdapter) conn; 287 if (sca.poolEntry == null) 288 return; // already released 289 ClientConnectionManager manager = sca.getManager(); 290 if (manager != null && manager != this) { 291 throw new IllegalArgumentException 292 ("Connection not obtained from this manager."); 293 } 294 295 try { 296 // BEGIN android-changed 297 // When recycling a Socket, we un-tag it to avoid collecting 298 // statistics from future users. 299 final Socket socket = uniquePoolEntry.connection.getSocket(); 300 if (socket != null) { 301 SocketTagger.get().untag(socket); 302 } 303 // END android-changed 304 305 // make sure that the response has been read completely 306 if (sca.isOpen() && (this.alwaysShutDown || 307 !sca.isMarkedReusable()) 308 ) { 309 if (log.isDebugEnabled()) { 310 log.debug 311 ("Released connection open but not reusable."); 312 } 313 314 // make sure this connection will not be re-used 315 // we might have gotten here because of a shutdown trigger 316 // shutdown of the adapter also clears the tracked route 317 sca.shutdown(); 318 } 319 } catch (IOException iox) { 320 //@@@ log as warning? let pass? 321 if (log.isDebugEnabled()) 322 log.debug("Exception shutting down released connection.", 323 iox); 324 } finally { 325 sca.detach(); 326 managedConn = null; 327 lastReleaseTime = System.currentTimeMillis(); 328 if(validDuration > 0) 329 connectionExpiresTime = timeUnit.toMillis(validDuration) + lastReleaseTime; 330 else 331 connectionExpiresTime = Long.MAX_VALUE; 332 } 333 } // releaseConnection 334 335 public void closeExpiredConnections() { 336 if(System.currentTimeMillis() >= connectionExpiresTime) { 337 closeIdleConnections(0, TimeUnit.MILLISECONDS); 338 } 339 } 340 341 342 // non-javadoc, see interface ClientConnectionManager 343 public void closeIdleConnections(long idletime, TimeUnit tunit) { 344 assertStillUp(); 345 346 // idletime can be 0 or negative, no problem there 347 if (tunit == null) { 348 throw new IllegalArgumentException("Time unit must not be null."); 349 } 350 351 if ((managedConn == null) && uniquePoolEntry.connection.isOpen()) { 352 final long cutoff = 353 System.currentTimeMillis() - tunit.toMillis(idletime); 354 if (lastReleaseTime <= cutoff) { 355 try { 356 uniquePoolEntry.close(); 357 } catch (IOException iox) { 358 // ignore 359 log.debug("Problem closing idle connection.", iox); 360 } 361 } 362 } 363 } 364 365 366 // non-javadoc, see interface ClientConnectionManager 367 public void shutdown() { 368 369 this.isShutDown = true; 370 371 if (managedConn != null) 372 managedConn.detach(); 373 374 try { 375 if (uniquePoolEntry != null) // and connection open? 376 uniquePoolEntry.shutdown(); 377 } catch (IOException iox) { 378 // ignore 379 log.debug("Problem while shutting down manager.", iox); 380 } finally { 381 uniquePoolEntry = null; 382 } 383 } 384 385 386 /** 387 * Revokes the currently issued connection. 388 * The adapter gets disconnected, the connection will be shut down. 389 */ 390 protected void revokeConnection() { 391 if (managedConn == null) 392 return; 393 394 log.warn(MISUSE_MESSAGE); 395 396 managedConn.detach(); 397 398 try { 399 uniquePoolEntry.shutdown(); 400 } catch (IOException iox) { 401 // ignore 402 log.debug("Problem while shutting down connection.", iox); 403 } 404 } 405 406 407 /** 408 * The pool entry for this connection manager. 409 */ 410 protected class PoolEntry extends AbstractPoolEntry { 411 412 /** 413 * Creates a new pool entry. 414 * 415 */ 416 protected PoolEntry() { 417 super(SingleClientConnManager.this.connOperator, null); 418 } 419 420 /** 421 * Closes the connection in this pool entry. 422 */ 423 protected void close() 424 throws IOException { 425 426 shutdownEntry(); 427 if (connection.isOpen()) 428 connection.close(); 429 } 430 431 432 /** 433 * Shuts down the connection in this pool entry. 434 */ 435 protected void shutdown() 436 throws IOException { 437 438 shutdownEntry(); 439 if (connection.isOpen()) 440 connection.shutdown(); 441 } 442 443 } // class PoolEntry 444 445 446 447 /** 448 * The connection adapter used by this manager. 449 */ 450 protected class ConnAdapter extends AbstractPooledConnAdapter { 451 452 /** 453 * Creates a new connection adapter. 454 * 455 * @param entry the pool entry for the connection being wrapped 456 * @param route the planned route for this connection 457 */ 458 protected ConnAdapter(PoolEntry entry, HttpRoute route) { 459 super(SingleClientConnManager.this, entry); 460 markReusable(); 461 entry.route = route; 462 } 463 464 } 465 466 467 } // class SingleClientConnManager 468