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, or
    114      *                  <code>null</code> for the default registry
    115      */
    116     public SingleClientConnManager(HttpParams params,
    117                                    SchemeRegistry schreg) {
    118 
    119         if (schreg == null) {
    120             throw new IllegalArgumentException
    121                 ("Scheme registry must not be null.");
    122         }
    123         this.schemeRegistry  = schreg;
    124         this.connOperator    = createConnectionOperator(schreg);
    125         this.uniquePoolEntry = new PoolEntry();
    126         this.managedConn     = null;
    127         this.lastReleaseTime = -1L;
    128         this.alwaysShutDown  = false; //@@@ from params? as argument?
    129         this.isShutDown      = false;
    130 
    131     } // <constructor>
    132 
    133 
    134     @Override
    135     protected void finalize() throws Throwable {
    136         shutdown();
    137         super.finalize();
    138     }
    139 
    140 
    141     // non-javadoc, see interface ClientConnectionManager
    142     public SchemeRegistry getSchemeRegistry() {
    143         return this.schemeRegistry;
    144     }
    145 
    146 
    147     /**
    148      * Hook for creating the connection operator.
    149      * It is called by the constructor.
    150      * Derived classes can override this method to change the
    151      * instantiation of the operator.
    152      * The default implementation here instantiates
    153      * {@link DefaultClientConnectionOperator DefaultClientConnectionOperator}.
    154      *
    155      * @param schreg    the scheme registry to use, or <code>null</code>
    156      *
    157      * @return  the connection operator to use
    158      */
    159     protected ClientConnectionOperator
    160         createConnectionOperator(SchemeRegistry schreg) {
    161 
    162         return new DefaultClientConnectionOperator(schreg);
    163     }
    164 
    165 
    166     /**
    167      * Asserts that this manager is not shut down.
    168      *
    169      * @throws IllegalStateException    if this manager is shut down
    170      */
    171     protected final void assertStillUp()
    172         throws IllegalStateException {
    173 
    174         if (this.isShutDown)
    175             throw new IllegalStateException("Manager is shut down.");
    176     }
    177 
    178 
    179     public final ClientConnectionRequest requestConnection(
    180             final HttpRoute route,
    181             final Object state) {
    182 
    183         return new ClientConnectionRequest() {
    184 
    185             public void abortRequest() {
    186                 // Nothing to abort, since requests are immediate.
    187             }
    188 
    189             public ManagedClientConnection getConnection(
    190                     long timeout, TimeUnit tunit) {
    191                 return SingleClientConnManager.this.getConnection(
    192                         route, state);
    193             }
    194 
    195         };
    196     }
    197 
    198 
    199     /**
    200      * Obtains a connection.
    201      * This method does not block.
    202      *
    203      * @param route     where the connection should point to
    204      *
    205      * @return  a connection that can be used to communicate
    206      *          along the given route
    207      */
    208     public ManagedClientConnection getConnection(HttpRoute route, Object state) {
    209 
    210         if (route == null) {
    211             throw new IllegalArgumentException("Route may not be null.");
    212         }
    213         assertStillUp();
    214 
    215         if (log.isDebugEnabled()) {
    216             log.debug("Get connection for route " + route);
    217         }
    218 
    219         if (managedConn != null)
    220             revokeConnection();
    221 
    222         // check re-usability of the connection
    223         boolean recreate = false;
    224         boolean shutdown = false;
    225 
    226         // Kill the connection if it expired.
    227         closeExpiredConnections();
    228 
    229         if (uniquePoolEntry.connection.isOpen()) {
    230             RouteTracker tracker = uniquePoolEntry.tracker;
    231             shutdown = (tracker == null || // can happen if method is aborted
    232                         !tracker.toRoute().equals(route));
    233         } else {
    234             // If the connection is not open, create a new PoolEntry,
    235             // as the connection may have been marked not reusable,
    236             // due to aborts -- and the PoolEntry should not be reused
    237             // either.  There's no harm in recreating an entry if
    238             // the connection is closed.
    239             recreate = true;
    240         }
    241 
    242         if (shutdown) {
    243             recreate = true;
    244             try {
    245                 uniquePoolEntry.shutdown();
    246             } catch (IOException iox) {
    247                 log.debug("Problem shutting down connection.", iox);
    248             }
    249         }
    250 
    251         if (recreate)
    252             uniquePoolEntry = new PoolEntry();
    253 
    254         // BEGIN android-changed
    255         // When using a recycled Socket, we need to re-tag it with any
    256         // updated statistics options.
    257         try {
    258             final Socket socket = uniquePoolEntry.connection.getSocket();
    259             if (socket != null) {
    260                 SocketTagger.get().tag(socket);
    261             }
    262         } catch (IOException iox) {
    263             log.debug("Problem tagging socket.", iox);
    264         }
    265         // END android-changed
    266 
    267         managedConn = new ConnAdapter(uniquePoolEntry, route);
    268 
    269         return managedConn;
    270     }
    271 
    272 
    273     // non-javadoc, see interface ClientConnectionManager
    274     public void releaseConnection(ManagedClientConnection conn, long validDuration, TimeUnit timeUnit) {
    275         assertStillUp();
    276 
    277         if (!(conn instanceof ConnAdapter)) {
    278             throw new IllegalArgumentException
    279                 ("Connection class mismatch, " +
    280                  "connection not obtained from this manager.");
    281         }
    282 
    283         if (log.isDebugEnabled()) {
    284             log.debug("Releasing connection " + conn);
    285         }
    286 
    287         ConnAdapter sca = (ConnAdapter) conn;
    288         if (sca.poolEntry == null)
    289             return; // already released
    290         ClientConnectionManager manager = sca.getManager();
    291         if (manager != null && manager != this) {
    292             throw new IllegalArgumentException
    293                 ("Connection not obtained from this manager.");
    294         }
    295 
    296         try {
    297             // BEGIN android-changed
    298             // When recycling a Socket, we un-tag it to avoid collecting
    299             // statistics from future users.
    300             final Socket socket = uniquePoolEntry.connection.getSocket();
    301             if (socket != null) {
    302                 SocketTagger.get().untag(socket);
    303             }
    304             // END android-changed
    305 
    306             // make sure that the response has been read completely
    307             if (sca.isOpen() && (this.alwaysShutDown ||
    308                                  !sca.isMarkedReusable())
    309                 ) {
    310                 if (log.isDebugEnabled()) {
    311                     log.debug
    312                         ("Released connection open but not reusable.");
    313                 }
    314 
    315                 // make sure this connection will not be re-used
    316                 // we might have gotten here because of a shutdown trigger
    317                 // shutdown of the adapter also clears the tracked route
    318                 sca.shutdown();
    319             }
    320         } catch (IOException iox) {
    321             //@@@ log as warning? let pass?
    322             if (log.isDebugEnabled())
    323                 log.debug("Exception shutting down released connection.",
    324                           iox);
    325         } finally {
    326             sca.detach();
    327             managedConn = null;
    328             lastReleaseTime = System.currentTimeMillis();
    329             if(validDuration > 0)
    330                 connectionExpiresTime = timeUnit.toMillis(validDuration) + lastReleaseTime;
    331             else
    332                 connectionExpiresTime = Long.MAX_VALUE;
    333         }
    334     } // releaseConnection
    335 
    336     public void closeExpiredConnections() {
    337         if(System.currentTimeMillis() >= connectionExpiresTime) {
    338             closeIdleConnections(0, TimeUnit.MILLISECONDS);
    339         }
    340     }
    341 
    342 
    343     // non-javadoc, see interface ClientConnectionManager
    344     public void closeIdleConnections(long idletime, TimeUnit tunit) {
    345         assertStillUp();
    346 
    347         // idletime can be 0 or negative, no problem there
    348         if (tunit == null) {
    349             throw new IllegalArgumentException("Time unit must not be null.");
    350         }
    351 
    352         if ((managedConn == null) && uniquePoolEntry.connection.isOpen()) {
    353             final long cutoff =
    354                 System.currentTimeMillis() - tunit.toMillis(idletime);
    355             if (lastReleaseTime <= cutoff) {
    356                 try {
    357                     uniquePoolEntry.close();
    358                 } catch (IOException iox) {
    359                     // ignore
    360                     log.debug("Problem closing idle connection.", iox);
    361                 }
    362             }
    363         }
    364     }
    365 
    366 
    367     // non-javadoc, see interface ClientConnectionManager
    368     public void shutdown() {
    369 
    370         this.isShutDown = true;
    371 
    372         if (managedConn != null)
    373             managedConn.detach();
    374 
    375         try {
    376             if (uniquePoolEntry != null) // and connection open?
    377                 uniquePoolEntry.shutdown();
    378         } catch (IOException iox) {
    379             // ignore
    380             log.debug("Problem while shutting down manager.", iox);
    381         } finally {
    382             uniquePoolEntry = null;
    383         }
    384     }
    385 
    386 
    387     /**
    388      * Revokes the currently issued connection.
    389      * The adapter gets disconnected, the connection will be shut down.
    390      */
    391     protected void revokeConnection() {
    392         if (managedConn == null)
    393             return;
    394 
    395         log.warn(MISUSE_MESSAGE);
    396 
    397         managedConn.detach();
    398 
    399         try {
    400             uniquePoolEntry.shutdown();
    401         } catch (IOException iox) {
    402             // ignore
    403             log.debug("Problem while shutting down connection.", iox);
    404         }
    405     }
    406 
    407 
    408     /**
    409      * The pool entry for this connection manager.
    410      */
    411     protected class PoolEntry extends AbstractPoolEntry {
    412 
    413         /**
    414          * Creates a new pool entry.
    415          *
    416          */
    417         protected PoolEntry() {
    418             super(SingleClientConnManager.this.connOperator, null);
    419         }
    420 
    421         /**
    422          * Closes the connection in this pool entry.
    423          */
    424         protected void close()
    425             throws IOException {
    426 
    427             shutdownEntry();
    428             if (connection.isOpen())
    429                 connection.close();
    430         }
    431 
    432 
    433         /**
    434          * Shuts down the connection in this pool entry.
    435          */
    436         protected void shutdown()
    437             throws IOException {
    438 
    439             shutdownEntry();
    440             if (connection.isOpen())
    441                 connection.shutdown();
    442         }
    443 
    444     } // class PoolEntry
    445 
    446 
    447 
    448     /**
    449      * The connection adapter used by this manager.
    450      */
    451     protected class ConnAdapter extends AbstractPooledConnAdapter {
    452 
    453         /**
    454          * Creates a new connection adapter.
    455          *
    456          * @param entry   the pool entry for the connection being wrapped
    457          * @param route   the planned route for this connection
    458          */
    459         protected ConnAdapter(PoolEntry entry, HttpRoute route) {
    460             super(SingleClientConnManager.this, entry);
    461             markReusable();
    462             entry.route = route;
    463         }
    464 
    465     }
    466 
    467 
    468 } // class SingleClientConnManager
    469