org.sonatype.nexus.apachehttpclient.Hc4ProviderImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.sonatype.nexus.apachehttpclient.Hc4ProviderImpl.java

Source

/*
 * Sonatype Nexus (TM) Open Source Version
 * Copyright (c) 2007-2014 Sonatype, Inc.
 * All rights reserved. Includes the third-party code listed at http://links.sonatype.com/products/nexus/oss/attributions.
 *
 * This program and the accompanying materials are made available under the terms of the Eclipse Public License Version 1.0,
 * which accompanies this distribution and is available at http://www.eclipse.org/legal/epl-v10.html.
 *
 * Sonatype Nexus (TM) Professional Version is available from Sonatype, Inc. "Sonatype" and "Sonatype Nexus" are trademarks
 * of Sonatype, Inc. Apache Maven is a trademark of the Apache Software Foundation. M2eclipse is a trademark of the
 * Eclipse Foundation. All other trademarks are the property of their respective owners.
 */
package org.sonatype.nexus.apachehttpclient;

import java.util.List;
import java.util.concurrent.TimeUnit;

import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;

import org.sonatype.nexus.configuration.application.ApplicationConfiguration;
import org.sonatype.nexus.configuration.application.GlobalRemoteConnectionSettings;
import org.sonatype.nexus.proxy.events.NexusStoppedEvent;
import org.sonatype.nexus.proxy.storage.remote.RemoteStorageContext;
import org.sonatype.nexus.proxy.utils.UserAgentBuilder;
import org.sonatype.nexus.util.SystemPropertiesHelper;
import org.sonatype.sisu.goodies.eventbus.EventBus;

import com.google.common.base.Preconditions;
import com.google.common.eventbus.Subscribe;
import com.google.common.primitives.Ints;
import org.apache.http.client.HttpClient;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.NoConnectionReuseStrategy;
import org.apache.http.impl.conn.PoolingClientConnectionManager;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;

/**
 * Default implementation of {@link Hc4Provider}.
 *
 * @author cstamas
 * @since 2.2
 */
@Singleton
@Named
public class Hc4ProviderImpl extends Hc4ProviderBase implements Hc4Provider {

    /**
     * Key for customizing connection pool maximum size. Value should be integer equal to 0 or greater. Pool size of 0
     * will actually prevent use of pool. Any positive number means the actual size of the pool to be created. This is
     * a
     * hard limit, connection pool will never contain more than this count of open sockets.
     */
    private static final String CONNECTION_POOL_MAX_SIZE_KEY = "nexus.apacheHttpClient4x.connectionPoolMaxSize";

    /**
     * Default pool max size: 200.
     */
    private static final int CONNECTION_POOL_MAX_SIZE_DEFAULT = 200;

    /**
     * Key for customizing connection pool size per route (usually per-repository, but not quite in case of Mirrors).
     * Value should be integer equal to 0 or greater. Pool size of 0 will actually prevent use of pool. Any positive
     * number means the actual size of the pool to be created.
     */
    private static final String CONNECTION_POOL_SIZE_KEY = "nexus.apacheHttpClient4x.connectionPoolSize";

    /**
     * Default pool size: 20.
     */
    private static final int CONNECTION_POOL_SIZE_DEFAULT = 20;

    /**
     * Key for customizing connection pool idle time. In other words, how long open connections (sockets) are kept in
     * pool idle (unused) before they get evicted and closed. Value is milliseconds.
     */
    private static final String CONNECTION_POOL_IDLE_TIME_KEY = "nexus.apacheHttpClient4x.connectionPoolIdleTime";

    /**
     * Default pool idle time: 30 seconds.
     */
    private static final long CONNECTION_POOL_IDLE_TIME_DEFAULT = TimeUnit.SECONDS.toMillis(30);

    /**
     * Key for customizing connection pool timeout. In other words, how long should a HTTP request execution be blocked
     * when pool is depleted, for a connection. Value is milliseconds.
     */
    private static final String CONNECTION_POOL_TIMEOUT_KEY = "nexus.apacheHttpClient4x.connectionPoolTimeout";

    /**
     * Default pool timeout: 30 seconds.
     */
    private static final long CONNECTION_POOL_TIMEOUT_DEFAULT = TimeUnit.SECONDS.toMillis(30);

    // ==

    /**
     * Application configuration holding the {@link GlobalRemoteConnectionSettings}.
     */
    private final ApplicationConfiguration applicationConfiguration;

    /**
     * The low level core event bus.
     */
    private final EventBus eventBus;

    /**
     * Shared client connection manager.
     */
    private final ManagedClientConnectionManager sharedConnectionManager;

    /**
     * Thread evicting idle open connections from {@link #sharedConnectionManager}.
     */
    private final EvictingThread evictingThread;

    /**
     * Used to install created {@link PoolingClientConnectionManager} into jmx.
     */
    private final PoolingClientConnectionManagerMBeanInstaller jmxInstaller;

    /**
     * @param applicationConfiguration the Nexus {@link ApplicationConfiguration}, must not be {@code null}.
     * @param userAgentBuilder         UA builder component, must not be {@code null}.
     * @param eventBus                 the event bus, must not be {@code null}.
     * @param jmxInstaller             installer to expose pool information over JMX, must not be {@code null}.
     * @param selectors                list of {@link SSLContextSelector}, might be {@code null}.
     */
    @Inject
    public Hc4ProviderImpl(final ApplicationConfiguration applicationConfiguration,
            final UserAgentBuilder userAgentBuilder, final EventBus eventBus,
            final PoolingClientConnectionManagerMBeanInstaller jmxInstaller,
            final List<SSLContextSelector> selectors) {
        super(userAgentBuilder);
        this.applicationConfiguration = Preconditions.checkNotNull(applicationConfiguration);
        this.jmxInstaller = Preconditions.checkNotNull(jmxInstaller);
        this.sharedConnectionManager = createClientConnectionManager(selectors);
        this.evictingThread = new EvictingThread(sharedConnectionManager, getConnectionPoolIdleTime());
        this.evictingThread.start();
        this.eventBus = Preconditions.checkNotNull(eventBus);
        this.eventBus.register(this);
        this.jmxInstaller.register(sharedConnectionManager);
        log.info(
                "Started (connectionPoolMaxSize {}, connectionPoolSize {}, connectionPoolIdleTime {} ms, connectionPoolTimeout {} ms, keepAliveMaxDuration {} ms)",
                getConnectionPoolMaxSize(), getConnectionPoolSize(), getConnectionPoolIdleTime(),
                getConnectionPoolTimeout(), getKeepAliveMaxDuration());
    }

    // configuration

    /**
     * Returns the pool max size.
     */
    protected int getConnectionPoolMaxSize() {
        return SystemPropertiesHelper.getInteger(CONNECTION_POOL_MAX_SIZE_KEY, CONNECTION_POOL_MAX_SIZE_DEFAULT);
    }

    /**
     * Returns the pool size per route.
     */
    protected int getConnectionPoolSize() {
        return SystemPropertiesHelper.getInteger(CONNECTION_POOL_SIZE_KEY, CONNECTION_POOL_SIZE_DEFAULT);
    }

    /**
     * Returns the connection pool idle (idle as unused but pooled) time in milliseconds.
     */
    protected long getConnectionPoolIdleTime() {
        return SystemPropertiesHelper.getLong(CONNECTION_POOL_IDLE_TIME_KEY, CONNECTION_POOL_IDLE_TIME_DEFAULT);
    }

    /**
     * Returns the pool timeout in milliseconds.
     */
    protected long getConnectionPoolTimeout() {
        return SystemPropertiesHelper.getLong(CONNECTION_POOL_TIMEOUT_KEY, CONNECTION_POOL_TIMEOUT_DEFAULT);
    }

    // ==

    /**
     * Performs a clean shutdown on this component, it kills the evicting thread and shuts down the shared connection
     * manager. Multiple invocation of this method is safe, it will not do anything.
     */
    public synchronized void shutdown() {
        evictingThread.interrupt();
        jmxInstaller.unregister();
        sharedConnectionManager._shutdown();
        eventBus.unregister(this);
        log.info("Stopped");
    }

    @Subscribe
    public void onEvent(final NexusStoppedEvent evt) {
        shutdown();
    }

    // ==

    /**
     * Safety net to prevent thread leaks (in non-production environment, mainly for ITs or UTs).
     */
    @Override
    protected void finalize() throws Throwable {
        try {
            shutdown();
        } finally {
            super.finalize();
        }
    }

    // == Hc4Provider API

    @Override
    public HttpClient createHttpClient() {
        // connection manager will cap the max count of connections, but with this below
        // we get rid of pooling. Pooling is used in Proxy repositories only, as all other
        // components using the "shared" httpClient should not produce hiw rate of requests
        // anyway, as they usually happen per user interactions (GPG gets keys are staging repo is closed, if not cached
        // yet, LVO gets info when UI's main window is loaded into user's browser, etc
        // ==
        // NEXUS-6220: This story above is mainly true and is basically "resource optimization", as
        // this method is used by various "side services" (typically LVO etc), where single request is made
        // with huge pauses in between. Still, connection reuse is needed in some rare cases,
        // like when you have NTLM proxy in between Nexus and the Internet. So, let ask HC4 provider,
        // does it "think" we still need connection reuse or not.
        return createHttpClient(reuseConnectionsNeeded(applicationConfiguration.getGlobalRemoteStorageContext()));
    }

    @Override
    public HttpClient createHttpClient(final boolean reuseConnections) {
        final Builder builder = prepareHttpClient(applicationConfiguration.getGlobalRemoteStorageContext());
        if (!reuseConnections) {
            builder.getHttpClientBuilder().setConnectionReuseStrategy(new NoConnectionReuseStrategy());
        }
        return builder.build();
    }

    @Override
    public HttpClient createHttpClient(final RemoteStorageContext context) {
        return prepareHttpClient(context).build();
    }

    @Override
    public Builder prepareHttpClient(final RemoteStorageContext context) {
        return prepareHttpClient(context, sharedConnectionManager);
    }

    // ==

    @Override
    protected void applyConfig(final Builder builder, final RemoteStorageContext context) {
        super.applyConfig(builder, context);
        builder.getRequestConfigBuilder().setConnectionRequestTimeout(Ints.checkedCast(getConnectionPoolTimeout()));
    }

    protected ManagedClientConnectionManager createClientConnectionManager(final List<SSLContextSelector> selectors)
            throws IllegalStateException {
        final Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", PlainConnectionSocketFactory.getSocketFactory())
                .register("https",
                        new NexusSSLConnectionSocketFactory(
                                (javax.net.ssl.SSLSocketFactory) javax.net.ssl.SSLSocketFactory.getDefault(),
                                SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER, selectors))
                .build();

        final ManagedClientConnectionManager connManager = new ManagedClientConnectionManager(registry);
        final int maxConnectionCount = getConnectionPoolMaxSize();
        final int perRouteConnectionCount = Math.min(getConnectionPoolSize(), maxConnectionCount);

        connManager.setMaxTotal(maxConnectionCount);
        connManager.setDefaultMaxPerRoute(perRouteConnectionCount);

        return connManager;
    }

    private class ManagedClientConnectionManager extends PoolingHttpClientConnectionManager {

        public ManagedClientConnectionManager(final Registry<ConnectionSocketFactory> schemeRegistry) {
            super(schemeRegistry);
        }

        @Override
        public void shutdown() {
            // do nothing in order to avoid unwanted shutdown of shared connection manager
        }

        private void _shutdown() {
            super.shutdown();
        }
    }

}