org.sonatype.nexus.client.rest.jersey.NexusClientFactoryImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.sonatype.nexus.client.rest.jersey.NexusClientFactoryImpl.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.client.rest.jersey;

import java.net.URI;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.List;

import javax.ws.rs.core.MediaType;

import org.sonatype.nexus.client.core.Condition;
import org.sonatype.nexus.client.core.NexusClient;
import org.sonatype.nexus.client.core.condition.NexusStatusConditions;
import org.sonatype.nexus.client.core.spi.SubsystemProvider;
import org.sonatype.nexus.client.internal.rest.NexusXStreamFactory;
import org.sonatype.nexus.client.internal.rest.XStreamXmlProvider;
import org.sonatype.nexus.client.internal.util.Version;
import org.sonatype.nexus.client.rest.AuthenticationInfo;
import org.sonatype.nexus.client.rest.BaseUrl;
import org.sonatype.nexus.client.rest.ConnectionInfo;
import org.sonatype.nexus.client.rest.NexusClientFactory;
import org.sonatype.nexus.client.rest.ProxyInfo;
import org.sonatype.nexus.client.rest.UsernamePasswordAuthenticationInfo;
import org.sonatype.sisu.siesta.client.filters.RequestFilters;

import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;
import com.sun.jersey.api.client.ClientHandlerException;
import com.sun.jersey.api.client.filter.LoggingFilter;
import com.sun.jersey.client.apache4.ApacheHttpClient4;
import com.sun.jersey.client.apache4.config.ApacheHttpClient4Config;
import com.sun.jersey.client.apache4.config.DefaultApacheHttpClient4Config;
import com.thoughtworks.xstream.XStream;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.ssl.AllowAllHostnameVerifier;
import org.apache.http.conn.ssl.BrowserCompatHostnameVerifier;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.conn.ssl.StrictHostnameVerifier;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.conn.ssl.X509HostnameVerifier;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.conn.PoolingClientConnectionManager;
import org.apache.http.impl.conn.SchemeRegistryFactory;
import org.apache.http.params.CoreProtocolPNames;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static com.sun.jersey.api.client.config.ClientConfig.PROPERTY_FOLLOW_REDIRECTS;
import static com.sun.jersey.client.apache4.config.ApacheHttpClient4Config.PROPERTY_CREDENTIALS_PROVIDER;
import static com.sun.jersey.client.apache4.config.ApacheHttpClient4Config.PROPERTY_PREEMPTIVE_BASIC_AUTHENTICATION;
import static com.sun.jersey.client.apache4.config.ApacheHttpClient4Config.PROPERTY_PROXY_PASSWORD;
import static com.sun.jersey.client.apache4.config.ApacheHttpClient4Config.PROPERTY_PROXY_URI;
import static com.sun.jersey.client.apache4.config.ApacheHttpClient4Config.PROPERTY_PROXY_USERNAME;
import static org.apache.http.conn.params.ConnRoutePNames.DEFAULT_PROXY;

/**
 * Jersey based {@link NexusClientFactory}.
 *
 * @since 2.7
 */
public class NexusClientFactoryImpl implements NexusClientFactory {

    private static final Logger LOG = LoggerFactory.getLogger(NexusClientFactoryImpl.class);

    /**
     * Modified "content-type" used by Nexus Client: it enforces body encoding too for UTF8.
     */
    private static final MediaType APPLICATION_XML_UTF8_TYPE = MediaType.valueOf("application/xml; charset=UTF-8");

    private final Condition connectionCondition;

    private final List<SubsystemProvider> subsystemProviders;

    public NexusClientFactoryImpl(final List<SubsystemProvider> subsystemProviders) {
        this(NexusStatusConditions.anyModern(), subsystemProviders);
    }

    public NexusClientFactoryImpl(final Condition connectionCondition,
            final List<SubsystemProvider> subsystemProviders) {
        this.connectionCondition = Preconditions.checkNotNull(connectionCondition, "connectionCondition");
        this.subsystemProviders = Preconditions.checkNotNull(subsystemProviders, "subsystemProviders");
    }

    @Override
    public final NexusClient createFor(final BaseUrl baseUrl) {
        return createFor(baseUrl, null);
    }

    @Override
    public final NexusClient createFor(final BaseUrl baseUrl, final AuthenticationInfo authenticationInfo) {
        return createFor(new ConnectionInfo(baseUrl, authenticationInfo, null));
    }

    @Override
    public final NexusClient createFor(final ConnectionInfo connectionInfo) {
        // we are java2java client, so we use XML instead of JSON, as
        // some current Nexus are one way only! So, we fix for XML
        final XStream xstream = new NexusXStreamFactory().createAndConfigureForXml();

        // we use XML for communication (unlike web browsers do, for which JSON makes more sense)
        return new JerseyNexusClient(connectionCondition, subsystemProviders, connectionInfo, xstream,
                doCreateHttpClientFor(connectionInfo, xstream), APPLICATION_XML_UTF8_TYPE);
    }

    protected ApacheHttpClient4 doCreateHttpClientFor(final ConnectionInfo connectionInfo, final XStream xstream) {
        final ApacheHttpClient4Config config = new DefaultApacheHttpClient4Config();
        config.getSingletons().add(new XStreamXmlProvider(xstream, APPLICATION_XML_UTF8_TYPE));
        // set _real_ URL for baseUrl, and not a redirection (typically http instead of https)
        config.getProperties().put(PROPERTY_FOLLOW_REDIRECTS, Boolean.FALSE);

        applyAuthenticationIfAny(connectionInfo, config);
        applyProxyIfAny(connectionInfo, config);

        // obey JSSE defined system properties
        config.getProperties().put(ApacheHttpClient4Config.PROPERTY_CONNECTION_MANAGER,
                new PoolingClientConnectionManager(SchemeRegistryFactory.createSystemDefault()));

        final ApacheHttpClient4 client = ApacheHttpClient4.create(config);

        // set UA
        client.getClientHandler().getHttpClient().getParams().setParameter(CoreProtocolPNames.USER_AGENT,
                "Nexus-Client/" + discoverClientVersion());

        // "tweak" HTTPS scheme as requested
        final TrustStrategy trustStrategy;
        switch (connectionInfo.getSslCertificateValidation()) {
        case NONE:
            trustStrategy = new TrustStrategy() {
                @Override
                public boolean isTrusted(final X509Certificate[] chain, final String authType)
                        throws CertificateException {
                    return true;
                }
            };
            break;
        case LAX:
            trustStrategy = new TrustSelfSignedStrategy();
            break;
        default:
            trustStrategy = null;
        }

        final X509HostnameVerifier hostnameVerifier;
        switch (connectionInfo.getSslCertificateHostnameValidation()) {
        case NONE:
            hostnameVerifier = new AllowAllHostnameVerifier();
            break;
        case STRICT:
            hostnameVerifier = new StrictHostnameVerifier();
            break;
        default:
            hostnameVerifier = new BrowserCompatHostnameVerifier();
        }

        try {
            final SSLSocketFactory ssf = new SSLSocketFactory(trustStrategy, hostnameVerifier);
            final Scheme tweakedHttpsScheme = new Scheme("https", 443, ssf);
            client.getClientHandler().getHttpClient().getConnectionManager().getSchemeRegistry()
                    .register(tweakedHttpsScheme);
        } catch (Exception e) {
            Throwables.propagate(e);
        }

        // NXCM-4547 JERSEY-1293 Enforce proxy setting on httpclient
        enforceProxyUri(config, client);

        if (LOG.isDebugEnabled()) {
            client.addFilter(new LoggingFilter());
        }

        client.addFilter(new RequestFilters());

        return client;
    }

    protected String discoverClientVersion() {
        return Version.readVersion("META-INF/maven/org.sonatype.nexus/nexus-client-core/pom.properties", "unknown");
    }

    // ==

    /**
     * NXCM-4547 JERSEY-1293 Enforce proxy setting on httpclient
     * <p/>
     * Revisit for jersey 1.13.
     */
    private void enforceProxyUri(final ApacheHttpClient4Config config, final ApacheHttpClient4 client) {
        final Object proxyProperty = config.getProperties().get(PROPERTY_PROXY_URI);
        if (proxyProperty != null) {
            final URI uri = getProxyUri(proxyProperty);
            final HttpHost proxy = new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme());
            client.getClientHandler().getHttpClient().getParams().setParameter(DEFAULT_PROXY, proxy);
        }
    }

    private static URI getProxyUri(final Object proxy) {
        if (proxy instanceof URI) {
            return (URI) proxy;
        } else if (proxy instanceof String) {
            return URI.create((String) proxy);
        } else {
            throw new ClientHandlerException(
                    "The proxy URI (" + PROPERTY_PROXY_URI + ") property MUST be an instance of String or URI");
        }
    }

    // ==

    protected void applyAuthenticationIfAny(final ConnectionInfo connectionInfo, ApacheHttpClient4Config config) {
        if (connectionInfo.getAuthenticationInfo() != null) {
            if (connectionInfo.getAuthenticationInfo() instanceof UsernamePasswordAuthenticationInfo) {
                final UsernamePasswordAuthenticationInfo upinfo = (UsernamePasswordAuthenticationInfo) connectionInfo
                        .getAuthenticationInfo();
                final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
                credentialsProvider.setCredentials(AuthScope.ANY,
                        new UsernamePasswordCredentials(upinfo.getUsername(), upinfo.getPassword()));
                config.getProperties().put(PROPERTY_CREDENTIALS_PROVIDER, credentialsProvider);
                config.getProperties().put(PROPERTY_PREEMPTIVE_BASIC_AUTHENTICATION, true);
            } else {
                throw new IllegalArgumentException(String.format("AuthenticationInfo of type %s is not supported!",
                        connectionInfo.getAuthenticationInfo().getClass().getName()));
            }
        }
    }

    protected void applyProxyIfAny(final ConnectionInfo connectionInfo, ApacheHttpClient4Config config) {
        if (connectionInfo.getProxyInfos().size() > 0) {
            final ProxyInfo proxyInfo = connectionInfo.getProxyInfos()
                    .get(connectionInfo.getBaseUrl().getProtocol());
            if (proxyInfo != null) {
                config.getProperties().put(PROPERTY_PROXY_URI,
                        "http://" + proxyInfo.getProxyHost() + ":" + proxyInfo.getProxyPort());

                if (proxyInfo.getProxyAuthentication() != null) {
                    if (proxyInfo.getProxyAuthentication() instanceof UsernamePasswordAuthenticationInfo) {
                        final UsernamePasswordAuthenticationInfo upinfo = (UsernamePasswordAuthenticationInfo) connectionInfo
                                .getAuthenticationInfo();
                        config.getProperties().put(PROPERTY_PROXY_USERNAME, upinfo.getUsername());
                        config.getProperties().put(PROPERTY_PROXY_PASSWORD, upinfo.getPassword());
                    } else {
                        throw new IllegalArgumentException(
                                String.format("AuthenticationInfo of type %s is not supported!",
                                        connectionInfo.getAuthenticationInfo().getClass().getName()));
                    }
                }
            } else {
                throw new IllegalArgumentException("ProxyInfo and BaseUrl protocols does not align!");
            }
        }
    }
}