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: * 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: }