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