michael@0: /* michael@0: * ==================================================================== michael@0: * Licensed to the Apache Software Foundation (ASF) under one michael@0: * or more contributor license agreements. See the NOTICE file michael@0: * distributed with this work for additional information michael@0: * regarding copyright ownership. The ASF licenses this file michael@0: * to you under the Apache License, Version 2.0 (the michael@0: * "License"); you may not use this file except in compliance michael@0: * with the License. You may obtain a copy of the License at michael@0: * michael@0: * http://www.apache.org/licenses/LICENSE-2.0 michael@0: * michael@0: * Unless required by applicable law or agreed to in writing, michael@0: * software distributed under the License is distributed on an michael@0: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY michael@0: * KIND, either express or implied. See the License for the michael@0: * specific language governing permissions and limitations michael@0: * under the License. michael@0: * ==================================================================== michael@0: * michael@0: * This software consists of voluntary contributions made by many michael@0: * individuals on behalf of the Apache Software Foundation. For more michael@0: * information on the Apache Software Foundation, please see michael@0: * . michael@0: * michael@0: */ michael@0: michael@0: package ch.boye.httpclientandroidlib.impl.conn; michael@0: michael@0: import java.io.IOException; michael@0: import java.util.concurrent.TimeUnit; michael@0: michael@0: import ch.boye.httpclientandroidlib.annotation.GuardedBy; michael@0: import ch.boye.httpclientandroidlib.annotation.ThreadSafe; michael@0: michael@0: import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; michael@0: /* LogFactory removed by HttpClient for Android script. */ michael@0: import ch.boye.httpclientandroidlib.conn.ClientConnectionManager; michael@0: import ch.boye.httpclientandroidlib.conn.ClientConnectionOperator; michael@0: import ch.boye.httpclientandroidlib.conn.ClientConnectionRequest; michael@0: import ch.boye.httpclientandroidlib.conn.ManagedClientConnection; michael@0: import ch.boye.httpclientandroidlib.conn.routing.HttpRoute; michael@0: import ch.boye.httpclientandroidlib.conn.routing.RouteTracker; michael@0: import ch.boye.httpclientandroidlib.conn.scheme.SchemeRegistry; michael@0: import ch.boye.httpclientandroidlib.params.HttpParams; michael@0: michael@0: /** michael@0: * A connection manager for a single connection. This connection manager michael@0: * maintains only one active connection at a time. Even though this class michael@0: * is thread-safe it ought to be used by one execution thread only. michael@0: *

michael@0: * SingleClientConnManager will make an effort to reuse the connection michael@0: * for subsequent requests with the same {@link HttpRoute route}. michael@0: * It will, however, close the existing connection and open it michael@0: * for the given route, if the route of the persistent connection does michael@0: * not match that of the connection request. If the connection has been michael@0: * already been allocated {@link IllegalStateException} is thrown. michael@0: * michael@0: * @since 4.0 michael@0: */ michael@0: @ThreadSafe michael@0: public class SingleClientConnManager implements ClientConnectionManager { michael@0: michael@0: public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); michael@0: michael@0: /** The message to be logged on multiple allocation. */ michael@0: public final static String MISUSE_MESSAGE = michael@0: "Invalid use of SingleClientConnManager: connection still allocated.\n" + michael@0: "Make sure to release the connection before allocating another one."; michael@0: michael@0: /** The schemes supported by this connection manager. */ michael@0: protected final SchemeRegistry schemeRegistry; michael@0: michael@0: /** The operator for opening and updating connections. */ michael@0: protected final ClientConnectionOperator connOperator; michael@0: michael@0: /** Whether the connection should be shut down on release. */ michael@0: protected final boolean alwaysShutDown; michael@0: michael@0: /** The one and only entry in this pool. */ michael@0: @GuardedBy("this") michael@0: protected PoolEntry uniquePoolEntry; michael@0: michael@0: /** The currently issued managed connection, if any. */ michael@0: @GuardedBy("this") michael@0: protected ConnAdapter managedConn; michael@0: michael@0: /** The time of the last connection release, or -1. */ michael@0: @GuardedBy("this") michael@0: protected long lastReleaseTime; michael@0: michael@0: /** The time the last released connection expires and shouldn't be reused. */ michael@0: @GuardedBy("this") michael@0: protected long connectionExpiresTime; michael@0: michael@0: /** Indicates whether this connection manager is shut down. */ michael@0: protected volatile boolean isShutDown; michael@0: michael@0: /** michael@0: * Creates a new simple connection manager. michael@0: * michael@0: * @param params the parameters for this manager michael@0: * @param schreg the scheme registry michael@0: * michael@0: * @deprecated use {@link SingleClientConnManager#SingleClientConnManager(SchemeRegistry)} michael@0: */ michael@0: @Deprecated michael@0: public SingleClientConnManager(HttpParams params, michael@0: SchemeRegistry schreg) { michael@0: this(schreg); michael@0: } michael@0: /** michael@0: * Creates a new simple connection manager. michael@0: * michael@0: * @param schreg the scheme registry michael@0: */ michael@0: public SingleClientConnManager(final SchemeRegistry schreg) { michael@0: if (schreg == null) { michael@0: throw new IllegalArgumentException michael@0: ("Scheme registry must not be null."); michael@0: } michael@0: this.schemeRegistry = schreg; michael@0: this.connOperator = createConnectionOperator(schreg); michael@0: this.uniquePoolEntry = new PoolEntry(); michael@0: this.managedConn = null; michael@0: this.lastReleaseTime = -1L; michael@0: this.alwaysShutDown = false; //@@@ from params? as argument? michael@0: this.isShutDown = false; michael@0: } michael@0: michael@0: /** michael@0: * @since 4.1 michael@0: */ michael@0: public SingleClientConnManager() { michael@0: this(SchemeRegistryFactory.createDefault()); michael@0: } michael@0: michael@0: @Override michael@0: protected void finalize() throws Throwable { michael@0: try { michael@0: shutdown(); michael@0: } finally { // Make sure we call overridden method even if shutdown barfs michael@0: super.finalize(); michael@0: } michael@0: } michael@0: michael@0: public SchemeRegistry getSchemeRegistry() { michael@0: return this.schemeRegistry; michael@0: } michael@0: michael@0: /** michael@0: * Hook for creating the connection operator. michael@0: * It is called by the constructor. michael@0: * Derived classes can override this method to change the michael@0: * instantiation of the operator. michael@0: * The default implementation here instantiates michael@0: * {@link DefaultClientConnectionOperator DefaultClientConnectionOperator}. michael@0: * michael@0: * @param schreg the scheme registry to use, or null michael@0: * michael@0: * @return the connection operator to use michael@0: */ michael@0: protected ClientConnectionOperator michael@0: createConnectionOperator(SchemeRegistry schreg) { michael@0: return new DefaultClientConnectionOperator(schreg); michael@0: } michael@0: michael@0: /** michael@0: * Asserts that this manager is not shut down. michael@0: * michael@0: * @throws IllegalStateException if this manager is shut down michael@0: */ michael@0: protected final void assertStillUp() throws IllegalStateException { michael@0: if (this.isShutDown) michael@0: throw new IllegalStateException("Manager is shut down."); michael@0: } michael@0: michael@0: public final ClientConnectionRequest requestConnection( michael@0: final HttpRoute route, michael@0: final Object state) { michael@0: michael@0: return new ClientConnectionRequest() { michael@0: michael@0: public void abortRequest() { michael@0: // Nothing to abort, since requests are immediate. michael@0: } michael@0: michael@0: public ManagedClientConnection getConnection( michael@0: long timeout, TimeUnit tunit) { michael@0: return SingleClientConnManager.this.getConnection( michael@0: route, state); michael@0: } michael@0: michael@0: }; michael@0: } michael@0: michael@0: /** michael@0: * Obtains a connection. michael@0: * michael@0: * @param route where the connection should point to michael@0: * michael@0: * @return a connection that can be used to communicate michael@0: * along the given route michael@0: */ michael@0: public synchronized ManagedClientConnection getConnection(HttpRoute route, Object state) { michael@0: if (route == null) { michael@0: throw new IllegalArgumentException("Route may not be null."); michael@0: } michael@0: assertStillUp(); michael@0: michael@0: if (log.isDebugEnabled()) { michael@0: log.debug("Get connection for route " + route); michael@0: } michael@0: michael@0: if (managedConn != null) michael@0: throw new IllegalStateException(MISUSE_MESSAGE); michael@0: michael@0: // check re-usability of the connection michael@0: boolean recreate = false; michael@0: boolean shutdown = false; michael@0: michael@0: // Kill the connection if it expired. michael@0: closeExpiredConnections(); michael@0: michael@0: if (uniquePoolEntry.connection.isOpen()) { michael@0: RouteTracker tracker = uniquePoolEntry.tracker; michael@0: shutdown = (tracker == null || // can happen if method is aborted michael@0: !tracker.toRoute().equals(route)); michael@0: } else { michael@0: // If the connection is not open, create a new PoolEntry, michael@0: // as the connection may have been marked not reusable, michael@0: // due to aborts -- and the PoolEntry should not be reused michael@0: // either. There's no harm in recreating an entry if michael@0: // the connection is closed. michael@0: recreate = true; michael@0: } michael@0: michael@0: if (shutdown) { michael@0: recreate = true; michael@0: try { michael@0: uniquePoolEntry.shutdown(); michael@0: } catch (IOException iox) { michael@0: log.debug("Problem shutting down connection.", iox); michael@0: } michael@0: } michael@0: michael@0: if (recreate) michael@0: uniquePoolEntry = new PoolEntry(); michael@0: michael@0: managedConn = new ConnAdapter(uniquePoolEntry, route); michael@0: michael@0: return managedConn; michael@0: } michael@0: michael@0: public synchronized void releaseConnection( michael@0: ManagedClientConnection conn, michael@0: long validDuration, TimeUnit timeUnit) { michael@0: assertStillUp(); michael@0: michael@0: if (!(conn instanceof ConnAdapter)) { michael@0: throw new IllegalArgumentException michael@0: ("Connection class mismatch, " + michael@0: "connection not obtained from this manager."); michael@0: } michael@0: michael@0: if (log.isDebugEnabled()) { michael@0: log.debug("Releasing connection " + conn); michael@0: } michael@0: michael@0: ConnAdapter sca = (ConnAdapter) conn; michael@0: if (sca.poolEntry == null) michael@0: return; // already released michael@0: ClientConnectionManager manager = sca.getManager(); michael@0: if (manager != null && manager != this) { michael@0: throw new IllegalArgumentException michael@0: ("Connection not obtained from this manager."); michael@0: } michael@0: michael@0: try { michael@0: // make sure that the response has been read completely michael@0: if (sca.isOpen() && (this.alwaysShutDown || michael@0: !sca.isMarkedReusable()) michael@0: ) { michael@0: if (log.isDebugEnabled()) { michael@0: log.debug michael@0: ("Released connection open but not reusable."); michael@0: } michael@0: michael@0: // make sure this connection will not be re-used michael@0: // we might have gotten here because of a shutdown trigger michael@0: // shutdown of the adapter also clears the tracked route michael@0: sca.shutdown(); michael@0: } michael@0: } catch (IOException iox) { michael@0: if (log.isDebugEnabled()) michael@0: log.debug("Exception shutting down released connection.", michael@0: iox); michael@0: } finally { michael@0: sca.detach(); michael@0: managedConn = null; michael@0: lastReleaseTime = System.currentTimeMillis(); michael@0: if(validDuration > 0) michael@0: connectionExpiresTime = timeUnit.toMillis(validDuration) + lastReleaseTime; michael@0: else michael@0: connectionExpiresTime = Long.MAX_VALUE; michael@0: } michael@0: } michael@0: michael@0: public synchronized void closeExpiredConnections() { michael@0: if(System.currentTimeMillis() >= connectionExpiresTime) { michael@0: closeIdleConnections(0, TimeUnit.MILLISECONDS); michael@0: } michael@0: } michael@0: michael@0: public synchronized void closeIdleConnections(long idletime, TimeUnit tunit) { michael@0: assertStillUp(); michael@0: michael@0: // idletime can be 0 or negative, no problem there michael@0: if (tunit == null) { michael@0: throw new IllegalArgumentException("Time unit must not be null."); michael@0: } michael@0: michael@0: if ((managedConn == null) && uniquePoolEntry.connection.isOpen()) { michael@0: final long cutoff = michael@0: System.currentTimeMillis() - tunit.toMillis(idletime); michael@0: if (lastReleaseTime <= cutoff) { michael@0: try { michael@0: uniquePoolEntry.close(); michael@0: } catch (IOException iox) { michael@0: // ignore michael@0: log.debug("Problem closing idle connection.", iox); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: public synchronized void shutdown() { michael@0: michael@0: this.isShutDown = true; michael@0: michael@0: if (managedConn != null) michael@0: managedConn.detach(); michael@0: michael@0: try { michael@0: if (uniquePoolEntry != null) // and connection open? michael@0: uniquePoolEntry.shutdown(); michael@0: } catch (IOException iox) { michael@0: // ignore michael@0: log.debug("Problem while shutting down manager.", iox); michael@0: } finally { michael@0: uniquePoolEntry = null; michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * @deprecated no longer used michael@0: */ michael@0: @Deprecated michael@0: protected synchronized void revokeConnection() { michael@0: if (managedConn == null) michael@0: return; michael@0: managedConn.detach(); michael@0: try { michael@0: uniquePoolEntry.shutdown(); michael@0: } catch (IOException iox) { michael@0: // ignore michael@0: log.debug("Problem while shutting down connection.", iox); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * The pool entry for this connection manager. michael@0: */ michael@0: protected class PoolEntry extends AbstractPoolEntry { michael@0: michael@0: /** michael@0: * Creates a new pool entry. michael@0: * michael@0: */ michael@0: protected PoolEntry() { michael@0: super(SingleClientConnManager.this.connOperator, null); michael@0: } michael@0: michael@0: /** michael@0: * Closes the connection in this pool entry. michael@0: */ michael@0: protected void close() throws IOException { michael@0: shutdownEntry(); michael@0: if (connection.isOpen()) michael@0: connection.close(); michael@0: } michael@0: michael@0: /** michael@0: * Shuts down the connection in this pool entry. michael@0: */ michael@0: protected void shutdown() throws IOException { michael@0: shutdownEntry(); michael@0: if (connection.isOpen()) michael@0: connection.shutdown(); michael@0: } michael@0: michael@0: } michael@0: michael@0: /** michael@0: * The connection adapter used by this manager. michael@0: */ michael@0: protected class ConnAdapter extends AbstractPooledConnAdapter { michael@0: michael@0: /** michael@0: * Creates a new connection adapter. michael@0: * michael@0: * @param entry the pool entry for the connection being wrapped michael@0: * @param route the planned route for this connection michael@0: */ michael@0: protected ConnAdapter(PoolEntry entry, HttpRoute route) { michael@0: super(SingleClientConnManager.this, entry); michael@0: markReusable(); michael@0: entry.route = route; michael@0: } michael@0: michael@0: } michael@0: michael@0: }