/* * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/SingleClientConnManager.java $ * $Revision: 673450 $ * $Date: 2008-07-02 10:35:05 -0700 (Wed, 02 Jul 2008) $ * * ==================================================================== * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Software Foundation. For more * information on the Apache Software Foundation, please see * <http://www.apache.org/>. * */ package org.apache.http.impl.conn; import java.io.IOException; import java.util.concurrent.TimeUnit; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.http.conn.ClientConnectionManager; import org.apache.http.conn.ClientConnectionOperator; import org.apache.http.conn.ClientConnectionRequest; import org.apache.http.conn.ManagedClientConnection; import org.apache.http.conn.routing.HttpRoute; import org.apache.http.conn.routing.RouteTracker; import org.apache.http.conn.scheme.SchemeRegistry; import org.apache.http.params.HttpParams; /** * A connection "manager" for a single connection. * This manager is good only for single-threaded use. * Allocation <i>always</i> returns the connection immediately, * even if it has not been released after the previous allocation. * In that case, a {@link #MISUSE_MESSAGE warning} is logged * and the previously issued connection is revoked. * <p> * This class is derived from <code>SimpleHttpConnectionManager</code> * in HttpClient 3. See there for original authors. * </p> * * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> * @author <a href="mailto:becke@u.washington.edu">Michael Becke</a> * * * <!-- empty lines to avoid svn diff problems --> * @version $Revision: 673450 $ * * @since 4.0 */ public class SingleClientConnManager implements ClientConnectionManager { private final Log log = LogFactory.getLog(getClass()); /** The message to be logged on multiple allocation. */ public final static String MISUSE_MESSAGE = "Invalid use of SingleClientConnManager: connection still allocated.\n" + "Make sure to release the connection before allocating another one."; /** The schemes supported by this connection manager. */ protected SchemeRegistry schemeRegistry; /** The operator for opening and updating connections. */ protected ClientConnectionOperator connOperator; /** The one and only entry in this pool. */ protected PoolEntry uniquePoolEntry; /** The currently issued managed connection, if any. */ protected ConnAdapter managedConn; /** The time of the last connection release, or -1. */ protected long lastReleaseTime; /** The time the last released connection expires and shouldn't be reused. */ protected long connectionExpiresTime; /** Whether the connection should be shut down on release. */ protected boolean alwaysShutDown; /** Indicates whether this connection manager is shut down. */ protected volatile boolean isShutDown; /** * Creates a new simple connection manager. * * @param params the parameters for this manager * @param schreg the scheme registry, or * <code>null</code> for the default registry */ public SingleClientConnManager(HttpParams params, SchemeRegistry schreg) { if (schreg == null) { throw new IllegalArgumentException ("Scheme registry must not be null."); } this.schemeRegistry = schreg; this.connOperator = createConnectionOperator(schreg); this.uniquePoolEntry = new PoolEntry(); this.managedConn = null; this.lastReleaseTime = -1L; this.alwaysShutDown = false; //@@@ from params? as argument? this.isShutDown = false; } // <constructor> @Override protected void finalize() throws Throwable { shutdown(); super.finalize(); } // non-javadoc, see interface ClientConnectionManager public SchemeRegistry getSchemeRegistry() { return this.schemeRegistry; } /** * Hook for creating the connection operator. * It is called by the constructor. * Derived classes can override this method to change the * instantiation of the operator. * The default implementation here instantiates * {@link DefaultClientConnectionOperator DefaultClientConnectionOperator}. * * @param schreg the scheme registry to use, or <code>null</code> * * @return the connection operator to use */ protected ClientConnectionOperator createConnectionOperator(SchemeRegistry schreg) { return new DefaultClientConnectionOperator(schreg); } /** * Asserts that this manager is not shut down. * * @throws IllegalStateException if this manager is shut down */ protected final void assertStillUp() throws IllegalStateException { if (this.isShutDown) throw new IllegalStateException("Manager is shut down."); } public final ClientConnectionRequest requestConnection( final HttpRoute route, final Object state) { return new ClientConnectionRequest() { public void abortRequest() { // Nothing to abort, since requests are immediate. } public ManagedClientConnection getConnection( long timeout, TimeUnit tunit) { return SingleClientConnManager.this.getConnection( route, state); } }; } /** * Obtains a connection. * This method does not block. * * @param route where the connection should point to * * @return a connection that can be used to communicate * along the given route */ public ManagedClientConnection getConnection(HttpRoute route, Object state) { if (route == null) { throw new IllegalArgumentException("Route may not be null."); } assertStillUp(); if (log.isDebugEnabled()) { log.debug("Get connection for route " + route); } if (managedConn != null) revokeConnection(); // check re-usability of the connection boolean recreate = false; boolean shutdown = false; // Kill the connection if it expired. closeExpiredConnections(); if (uniquePoolEntry.connection.isOpen()) { RouteTracker tracker = uniquePoolEntry.tracker; shutdown = (tracker == null || // can happen if method is aborted !tracker.toRoute().equals(route)); } else { // If the connection is not open, create a new PoolEntry, // as the connection may have been marked not reusable, // due to aborts -- and the PoolEntry should not be reused // either. There's no harm in recreating an entry if // the connection is closed. recreate = true; } if (shutdown) { recreate = true; try { uniquePoolEntry.shutdown(); } catch (IOException iox) { log.debug("Problem shutting down connection.", iox); } } if (recreate) uniquePoolEntry = new PoolEntry(); managedConn = new ConnAdapter(uniquePoolEntry, route); return managedConn; } // non-javadoc, see interface ClientConnectionManager public void releaseConnection(ManagedClientConnection conn, long validDuration, TimeUnit timeUnit) { assertStillUp(); if (!(conn instanceof ConnAdapter)) { throw new IllegalArgumentException ("Connection class mismatch, " + "connection not obtained from this manager."); } if (log.isDebugEnabled()) { log.debug("Releasing connection " + conn); } ConnAdapter sca = (ConnAdapter) conn; if (sca.poolEntry == null) return; // already released ClientConnectionManager manager = sca.getManager(); if (manager != null && manager != this) { throw new IllegalArgumentException ("Connection not obtained from this manager."); } try { // make sure that the response has been read completely if (sca.isOpen() && (this.alwaysShutDown || !sca.isMarkedReusable()) ) { if (log.isDebugEnabled()) { log.debug ("Released connection open but not reusable."); } // make sure this connection will not be re-used // we might have gotten here because of a shutdown trigger // shutdown of the adapter also clears the tracked route sca.shutdown(); } } catch (IOException iox) { //@@@ log as warning? let pass? if (log.isDebugEnabled()) log.debug("Exception shutting down released connection.", iox); } finally { sca.detach(); managedConn = null; lastReleaseTime = System.currentTimeMillis(); if(validDuration > 0) connectionExpiresTime = timeUnit.toMillis(validDuration) + lastReleaseTime; else connectionExpiresTime = Long.MAX_VALUE; } } // releaseConnection public void closeExpiredConnections() { if(System.currentTimeMillis() >= connectionExpiresTime) { closeIdleConnections(0, TimeUnit.MILLISECONDS); } } // non-javadoc, see interface ClientConnectionManager public void closeIdleConnections(long idletime, TimeUnit tunit) { assertStillUp(); // idletime can be 0 or negative, no problem there if (tunit == null) { throw new IllegalArgumentException("Time unit must not be null."); } if ((managedConn == null) && uniquePoolEntry.connection.isOpen()) { final long cutoff = System.currentTimeMillis() - tunit.toMillis(idletime); if (lastReleaseTime <= cutoff) { try { uniquePoolEntry.close(); } catch (IOException iox) { // ignore log.debug("Problem closing idle connection.", iox); } } } } // non-javadoc, see interface ClientConnectionManager public void shutdown() { this.isShutDown = true; if (managedConn != null) managedConn.detach(); try { if (uniquePoolEntry != null) // and connection open? uniquePoolEntry.shutdown(); } catch (IOException iox) { // ignore log.debug("Problem while shutting down manager.", iox); } finally { uniquePoolEntry = null; } } /** * Revokes the currently issued connection. * The adapter gets disconnected, the connection will be shut down. */ protected void revokeConnection() { if (managedConn == null) return; log.warn(MISUSE_MESSAGE); managedConn.detach(); try { uniquePoolEntry.shutdown(); } catch (IOException iox) { // ignore log.debug("Problem while shutting down connection.", iox); } } /** * The pool entry for this connection manager. */ protected class PoolEntry extends AbstractPoolEntry { /** * Creates a new pool entry. * */ protected PoolEntry() { super(SingleClientConnManager.this.connOperator, null); } /** * Closes the connection in this pool entry. */ protected void close() throws IOException { shutdownEntry(); if (connection.isOpen()) connection.close(); } /** * Shuts down the connection in this pool entry. */ protected void shutdown() throws IOException { shutdownEntry(); if (connection.isOpen()) connection.shutdown(); } } // class PoolEntry /** * The connection adapter used by this manager. */ protected class ConnAdapter extends AbstractPooledConnAdapter { /** * Creates a new connection adapter. * * @param entry the pool entry for the connection being wrapped * @param route the planned route for this connection */ protected ConnAdapter(PoolEntry entry, HttpRoute route) { super(SingleClientConnManager.this, entry); markReusable(); entry.route = route; } } } // class SingleClientConnManager