/*
* 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!");
}
}
}
}