Home | History | Annotate | Download | only in conn
      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 (at) 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      */
    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
    134     protected void finalize() throws Throwable {
    135         shutdown();
    136         super.finalize();
    137     }
    138 
    139 
    140     // non-javadoc, see interface ClientConnectionManager
    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
    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      */
    170     protected final void assertStillUp()
    171         throws IllegalStateException {
    172 
    173         if (this.isShutDown)
    174             throw new IllegalStateException("Manager is shut down.");
    175     }
    176 
    177 
    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