Java tutorial
/* * (c) 2015 CenturyLink. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.centurylink.cloud.sdk.core.client; /** * @author Ilya Drabenia */ import com.centurylink.cloud.sdk.core.client.retry.ClcRetryStrategy; import org.apache.http.HttpHost; import org.apache.http.auth.AuthScope; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.HttpClient; import org.apache.http.client.params.HttpClientParams; import org.apache.http.conn.ClientConnectionManager; import org.apache.http.conn.scheme.PlainSocketFactory; import org.apache.http.conn.scheme.Scheme; import org.apache.http.conn.scheme.SchemeRegistry; import org.apache.http.conn.ssl.AllowAllHostnameVerifier; import org.apache.http.conn.ssl.BrowserCompatHostnameVerifier; import org.apache.http.conn.ssl.StrictHostnameVerifier; import org.apache.http.conn.ssl.X509HostnameVerifier; import org.apache.http.impl.client.AutoRetryHttpClient; import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.conn.BasicClientConnectionManager; import org.apache.http.impl.conn.PoolingClientConnectionManager; import org.apache.http.params.BasicHttpParams; import org.apache.http.params.HttpConnectionParams; import org.jboss.resteasy.client.jaxrs.ClientHttpEngine; import org.jboss.resteasy.client.jaxrs.ResteasyClient; import org.jboss.resteasy.client.jaxrs.engines.ApacheHttpClient4Engine; import org.jboss.resteasy.client.jaxrs.engines.PassthroughTrustManager; import org.jboss.resteasy.client.jaxrs.internal.ClientConfiguration; import org.jboss.resteasy.plugins.providers.RegisterBuiltin; import org.jboss.resteasy.spi.ResteasyProviderFactory; import javax.net.ssl.*; import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.core.Configuration; import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.security.KeyStore; import java.security.SecureRandom; import java.security.cert.X509Certificate; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; /** * Abstraction for creating Clients. Allows SSL configuration. Uses Apache Http Client under * the covers. If used with other ClientHttpEngines though, all configuration options are ignored. * * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @version $Revision: 1 $ */ @SuppressWarnings("deprecated") public class SdkClientBuilder extends ClientBuilder { public static enum HostnameVerificationPolicy { /** * Hostname verification is not done on the server's certificate */ ANY, /** * Allows wildcards in subdomain names i.e. *.foo.com */ WILDCARD, /** * CN must match hostname connecting to */ STRICT } protected KeyStore truststore; protected KeyStore clientKeyStore; protected String clientPrivateKeyPassword; protected boolean disableTrustManager; protected HostnameVerificationPolicy policy = HostnameVerificationPolicy.WILDCARD; protected ResteasyProviderFactory providerFactory; protected ExecutorService asyncExecutor; protected SSLContext sslContext; protected Map<String, Object> properties = new HashMap<String, Object>(); protected ClientHttpEngine httpEngine; protected int connectionPoolSize; protected int maxPooledPerRoute = 0; protected long connectionTTL = -1; protected TimeUnit connectionTTLUnit = TimeUnit.MILLISECONDS; protected long socketTimeout = -1; protected TimeUnit socketTimeoutUnits = TimeUnit.MILLISECONDS; protected long establishConnectionTimeout = -1; protected TimeUnit establishConnectionTimeoutUnits = TimeUnit.MILLISECONDS; protected int connectionCheckoutTimeoutMs = -1; protected HostnameVerifier verifier = null; protected HttpHost defaultProxy; protected int responseBufferSize; protected Integer maxRetries; protected UsernamePasswordCredentials proxyCredentials; /** * Changing the providerFactory will wipe clean any registered components or properties. * * @param providerFactory the provider factory * @return current instance of SDK client builder */ public SdkClientBuilder providerFactory(ResteasyProviderFactory providerFactory) { this.providerFactory = providerFactory; return this; } /** * Executor to use to run AsyncInvoker invocations * * @param asyncExecutor the executor service * @return current instance of SDK client builder */ public SdkClientBuilder asyncExecutor(ExecutorService asyncExecutor) { this.asyncExecutor = asyncExecutor; return this; } /** * If there is a connection pool, set the time to live in the pool. * * @param ttl the time to live * @param unit the time unit * @return current instance of SDK client builder */ public SdkClientBuilder connectionTTL(long ttl, TimeUnit unit) { this.connectionTTL = ttl; this.connectionTTLUnit = unit; return this; } /** * Socket inactivity timeout * * @param timeout the inactivity timeout * @param unit the time unit * @return current instance of SDK client builder */ public SdkClientBuilder socketTimeout(long timeout, TimeUnit unit) { this.socketTimeout = timeout; this.socketTimeoutUnits = unit; return this; } /** * When trying to make an initial socket connection, what is the timeout? * * @param timeout the timeout * @param unit the time unit * @return current instance of SDK client builder */ public SdkClientBuilder establishConnectionTimeout(long timeout, TimeUnit unit) { this.establishConnectionTimeout = timeout; this.establishConnectionTimeoutUnits = unit; return this; } /** * If connection pooling enabled, how many connections to pool per url? * * @param maxPooledPerRoute the maximum number of connections per pool * @return current instance of SDK client builder */ public SdkClientBuilder maxPooledPerRoute(int maxPooledPerRoute) { this.maxPooledPerRoute = maxPooledPerRoute; return this; } public SdkClientBuilder maxRetries(int maxRetries) { this.maxRetries = maxRetries; return this; } public SdkClientBuilder proxyConfig(String proxyHost, int proxyPort, String proxyScheme, String proxyUsername, String proxyPassword) { if (proxyHost != null) { defaultProxy(proxyHost, proxyPort, proxyScheme); if (proxyUsername != null) { proxyCredentials(proxyUsername, proxyPassword); } } return this; } /** * If connection pooling is enabled, how long will we wait to get a connection? * @param timeout the timeout * @param unit the units the timeout is in * @return this builder */ public SdkClientBuilder connectionCheckoutTimeout(long timeout, TimeUnit unit) { this.connectionCheckoutTimeoutMs = (int) TimeUnit.MILLISECONDS.convert(timeout, unit); return this; } /** * Number of connections allowed to pool * * @param connectionPoolSize the number of connections * @return current instance of SDK client builder */ public SdkClientBuilder connectionPoolSize(int connectionPoolSize) { this.connectionPoolSize = connectionPoolSize; return this; } /** * Response stream is wrapped in a BufferedInputStream. Default is 8192. Value of 0 will not wrap it. * Value of -1 will use a SelfExpandingBufferedInputStream * * @param size the response buffer size * @return current instance of SDK client builder */ public SdkClientBuilder responseBufferSize(int size) { this.responseBufferSize = size; return this; } /** * Disable trust management and hostname verification. <i>NOTE</i> this is a security * hole, so only set this option if you cannot or do not want to verify the identity of the * host you are communicating with. * @return current instance of SDK client builder */ public SdkClientBuilder disableTrustManager() { this.disableTrustManager = true; return this; } /** * SSL policy used to verify hostnames * * @param policy the hostname verification policy * @return current instance of SDK client builder */ public SdkClientBuilder hostnameVerification(HostnameVerificationPolicy policy) { this.policy = policy; return this; } /** * Negates all ssl and connection specific configuration * * @param httpEngine the client http engine * @return current instance of SDK client builder */ public SdkClientBuilder httpEngine(ClientHttpEngine httpEngine) { this.httpEngine = httpEngine; return this; } @Override public SdkClientBuilder sslContext(SSLContext sslContext) { this.sslContext = sslContext; return this; } @Override public SdkClientBuilder trustStore(KeyStore truststore) { this.truststore = truststore; return this; } @Override public SdkClientBuilder keyStore(KeyStore keyStore, String password) { this.clientKeyStore = keyStore; this.clientPrivateKeyPassword = password; return this; } @Override public SdkClientBuilder keyStore(KeyStore keyStore, char[] password) { this.clientKeyStore = keyStore; this.clientPrivateKeyPassword = new String(password); return this; } @Override public SdkClientBuilder property(String name, Object value) { getProviderFactory().property(name, value); return this; } /** * Specify a default proxy. Default port and schema will be used * * @param hostname the proxy hostname * @return current instance of SDK client builder */ public SdkClientBuilder defaultProxy(String hostname) { return defaultProxy(hostname, -1, null); } /** * Specify a default proxy host and port. Default schema will be used * * @param hostname the proxy hostname * @param port the proxy port number * @return current instance of SDK client builder */ public SdkClientBuilder defaultProxy(String hostname, int port) { return defaultProxy(hostname, port, null); } /** * Specify default proxy. * * @param hostname the proxy hostname * @param port the proxy port number * @param scheme the proxy scheme * @return current instance of SDK client builder */ public SdkClientBuilder defaultProxy(String hostname, int port, final String scheme) { this.defaultProxy = new HttpHost(hostname, port, scheme); return this; } /** * Specify proxy credentials. * @param user user * @param password password * @return current client builder */ public SdkClientBuilder proxyCredentials(String user, String password) { this.proxyCredentials = new UsernamePasswordCredentials(user, password); return this; } protected ResteasyProviderFactory getProviderFactory() { if (providerFactory == null) { // create a new one providerFactory = new ResteasyProviderFactory(); RegisterBuiltin.register(providerFactory); } return providerFactory; } @Override public ResteasyClient build() { ClientConfiguration config = new ClientConfiguration(getProviderFactory()); for (Map.Entry<String, Object> entry : properties.entrySet()) { config.property(entry.getKey(), entry.getValue()); } ExecutorService executor = asyncExecutor; boolean cleanupExecutor = false; if (executor == null) { cleanupExecutor = true; executor = Executors.newFixedThreadPool(10); } ClientHttpEngine engine = httpEngine; if (engine == null) { engine = initDefaultEngine(); } try { Constructor<ResteasyClient> constructor = ResteasyClient.class.getDeclaredConstructor( ClientHttpEngine.class, ExecutorService.class, boolean.class, ClientConfiguration.class); constructor.setAccessible(true); return constructor.newInstance(engine, executor, cleanupExecutor, config); } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException ex) { throw new ClcClientException(ex); } } static class VerifierWrapper implements X509HostnameVerifier { protected HostnameVerifier verifier; VerifierWrapper(HostnameVerifier verifier) { this.verifier = verifier; } @Override public void verify(String host, SSLSocket ssl) throws IOException { if (!verifier.verify(host, ssl.getSession())) { throw new SSLException("Hostname verification failure"); } } @Override public void verify(String host, X509Certificate cert) throws SSLException { throw new SSLException("This verification path not implemented"); } @Override public void verify(String host, String[] cns, String[] subjectAlts) throws SSLException { throw new SSLException("This verification path not implemented"); } @Override public boolean verify(String s, SSLSession sslSession) { return verifier.verify(s, sslSession); } } protected ClientHttpEngine initDefaultEngine() { HttpClient httpClient; X509HostnameVerifier verifier = initHostnameVerifier(); try { org.apache.http.conn.ssl.SSLSocketFactory sslsf; SSLContext theContext = sslContext; if (disableTrustManager) { theContext = SSLContext.getInstance("SSL"); theContext.init(null, new TrustManager[] { new PassthroughTrustManager() }, new SecureRandom()); verifier = new AllowAllHostnameVerifier(); sslsf = new org.apache.http.conn.ssl.SSLSocketFactory(theContext, verifier); } else if (theContext != null) { sslsf = new org.apache.http.conn.ssl.SSLSocketFactory(theContext, verifier); } else if (clientKeyStore != null || truststore != null) { sslsf = new org.apache.http.conn.ssl.SSLSocketFactory(org.apache.http.conn.ssl.SSLSocketFactory.TLS, clientKeyStore, clientPrivateKeyPassword, truststore, null, verifier); } else { final SSLContext tlsContext = SSLContext.getInstance(org.apache.http.conn.ssl.SSLSocketFactory.TLS); tlsContext.init(null, null, null); sslsf = new org.apache.http.conn.ssl.SSLSocketFactory(tlsContext, verifier); } SchemeRegistry registry = new SchemeRegistry(); registry.register(new Scheme("http", 80, PlainSocketFactory.getSocketFactory())); Scheme httpsScheme = new Scheme("https", 443, sslsf); registry.register(httpsScheme); httpClient = new AutoRetryHttpClient( new DefaultHttpClient(initClientConnectionManager(registry), initHttpParams()) { { if (proxyCredentials != null) { setCredentialsProvider(new BasicCredentialsProvider() { { setCredentials( new AuthScope(defaultProxy.getHostName(), defaultProxy.getPort()), proxyCredentials); } }); } } }, new ClcRetryStrategy(3, 1000)); ApacheHttpClient4Engine engine = new ApacheHttpClient4Engine(httpClient, true); engine.setResponseBufferSize(responseBufferSize); engine.setHostnameVerifier(verifier); // this may be null. We can't really support this with Apache Client. engine.setSslContext(theContext); engine.setDefaultProxy(defaultProxy); return engine; } catch (Exception e) { throw new RuntimeException(e); } } private X509HostnameVerifier initHostnameVerifier() { X509HostnameVerifier verifier = null; if (this.verifier != null) { verifier = new VerifierWrapper(this.verifier); } else { switch (policy) { case ANY: verifier = new AllowAllHostnameVerifier(); break; case WILDCARD: verifier = new BrowserCompatHostnameVerifier(); break; case STRICT: verifier = new StrictHostnameVerifier(); break; } } return verifier; } private ClientConnectionManager initClientConnectionManager(SchemeRegistry registry) { ClientConnectionManager cm; if (connectionPoolSize > 0) { PoolingClientConnectionManager tcm = new PoolingClientConnectionManager(registry, connectionTTL, connectionTTLUnit); tcm.setMaxTotal(connectionPoolSize); if (maxPooledPerRoute == 0) { maxPooledPerRoute = connectionPoolSize; } tcm.setDefaultMaxPerRoute(maxPooledPerRoute); cm = tcm; } else { cm = new BasicClientConnectionManager(registry); } return cm; } private BasicHttpParams initHttpParams() { BasicHttpParams params = new BasicHttpParams(); if (socketTimeout > -1) { HttpConnectionParams.setSoTimeout(params, (int) socketTimeoutUnits.toMillis(socketTimeout)); } if (establishConnectionTimeout > -1) { HttpConnectionParams.setConnectionTimeout(params, (int) establishConnectionTimeoutUnits.toMillis(establishConnectionTimeout)); } if (connectionCheckoutTimeoutMs > -1) { HttpClientParams.setConnectionManagerTimeout(params, connectionCheckoutTimeoutMs); } return params; } @Override public SdkClientBuilder hostnameVerifier(HostnameVerifier verifier) { this.verifier = verifier; return this; } @Override public Configuration getConfiguration() { return getProviderFactory().getConfiguration(); } @Override public SdkClientBuilder register(Class<?> componentClass) { getProviderFactory().register(componentClass); return this; } @Override public SdkClientBuilder register(Class<?> componentClass, int priority) { getProviderFactory().register(componentClass, priority); return this; } @Override public SdkClientBuilder register(Class<?> componentClass, Class<?>... contracts) { getProviderFactory().register(componentClass, contracts); return this; } @Override public SdkClientBuilder register(Class<?> componentClass, Map<Class<?>, Integer> contracts) { getProviderFactory().register(componentClass, contracts); return this; } @Override public SdkClientBuilder register(Object component) { getProviderFactory().register(component); return this; } @Override public SdkClientBuilder register(Object component, int priority) { getProviderFactory().register(component, priority); return this; } @Override public SdkClientBuilder register(Object component, Class<?>... contracts) { getProviderFactory().register(component, contracts); return this; } @Override public SdkClientBuilder register(Object component, Map<Class<?>, Integer> contracts) { getProviderFactory().register(component, contracts); return this; } @Override public SdkClientBuilder withConfig(Configuration config) { providerFactory = new ResteasyProviderFactory(); providerFactory.setProperties(config.getProperties()); for (Class clazz : config.getClasses()) { Map<Class<?>, Integer> contracts = config.getContracts(clazz); try { register(clazz, contracts); } catch (RuntimeException e) { throw new RuntimeException("failed on registering class: " + clazz.getName(), e); } } for (Object obj : config.getInstances()) { Map<Class<?>, Integer> contracts = config.getContracts(obj.getClass()); register(obj, contracts); } return this; } }