/******************************************************************************* * Copyright (c) 2010-2014 SAP AG and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * SAP AG - initial API and implementation *******************************************************************************/ package org.eclipse.skalli.core.destination; import java.net.URL; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.text.MessageFormat; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.math.NumberUtils; 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.client.HttpClient; import org.apache.http.conn.params.ConnRoutePNames; import org.apache.http.conn.scheme.Scheme; import org.apache.http.conn.scheme.SchemeRegistry; import org.apache.http.conn.ssl.SSLSocketFactory; import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; import org.apache.http.params.BasicHttpParams; import org.apache.http.params.HttpConnectionParams; import org.apache.http.params.HttpParams; import org.eclipse.skalli.services.configuration.ConfigurationService; import org.eclipse.skalli.services.destination.DestinationService; import org.osgi.service.component.ComponentConstants; import org.osgi.service.component.ComponentContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class DestinationComponent implements DestinationService { private static final Logger LOG = LoggerFactory.getLogger(DestinationComponent.class); private static final String HTTP = "http"; //$NON-NLS-1$ private static final String HTTPS = "https"; //$NON-NLS-1$ private static final String HTTP_PROXY_HOST = "http.proxyHost"; //$NON-NLS-1$ private static final String HTTP_PROXY_PORT = "http.proxyPort"; //$NON-NLS-1$ private static final String HTTPS_PROXY_HOST = "https.proxyHost"; //$NON-NLS-1$ private static final String HTTPS_PROXY_PORT = "https.proxyPort"; //$NON-NLS-1$ private static final String NON_PROXY_HOSTS = "http.nonProxyHosts"; //$NON-NLS-1$ @SuppressWarnings("nls") private static final String[] SSL_PROTOCOLS = new String[] { "TLS", "SSLv3", "SSLv2", "SSL" }; @SuppressWarnings("nls") private static final String[] RE_SEARCH = new String[] { ";", "*", "." }; @SuppressWarnings("nls") private static final String[] RE_REPLACE = new String[] { "|", "(\\w|\\.|\\-)*", "\\." }; // general timeout for connection requests private static final int CONNECT_TIMEOUT = 10000; private static final int READ_TIMEOUT = 300000; // connection manager settings private static final int MAX_TOTAL_CONNECTIONS = 100; private static final int MAX_CONNECTIONS_PER_ROUTE = 5; private ConfigurationService configService; private ThreadSafeClientConnManager connectionManager; protected void activate(ComponentContext context) { initializeConnectionManager(); LOG.info(MessageFormat.format("[DestinationService] {0} : activated", (String) context.getProperties().get(ComponentConstants.COMPONENT_NAME))); } protected void deactivate(ComponentContext context) { if (connectionManager != null) { connectionManager.shutdown(); } LOG.info(MessageFormat.format("[DestinationService] {0} : deactivated", (String) context.getProperties().get(ComponentConstants.COMPONENT_NAME))); } protected void bindConfigurationService(ConfigurationService configService) { LOG.info(MessageFormat.format("bindConfigurationService({0})", configService)); //$NON-NLS-1$ this.configService = configService; } protected void unbindConfigurationService(ConfigurationService configService) { LOG.info(MessageFormat.format("unbindConfigurationService({0})", configService)); //$NON-NLS-1$ this.configService = null; } @Override public HttpClient getClient(URL url) { if (!isSupportedProtocol(url)) { throw new IllegalArgumentException(MessageFormat.format( "Protocol ''{0}'' is not suppported by this method", url.getProtocol())); } HttpParams params = new BasicHttpParams(); HttpConnectionParams.setConnectionTimeout(params, CONNECT_TIMEOUT); HttpConnectionParams.setSoTimeout(params, READ_TIMEOUT); HttpConnectionParams.setTcpNoDelay(params, true); DefaultHttpClient client = new DefaultHttpClient(connectionManager, params); setProxy(client, url); setCredentials(client, url); return client; } /** * Returns <code>true</code>, if the given URL starts with a known * protocol specifier, i.e. <tt>http://</tt> or <tt>https://</tt>. * * @param url the URL to check. */ @Override public boolean isSupportedProtocol(URL url) { String protocol = url.getProtocol(); return protocol.equals(HTTP) || protocol.equals(HTTPS); } private void setCredentials(DefaultHttpClient client, URL url) { if (configService == null) { return; } DestinationsConfig config = configService.readConfiguration(DestinationsConfig.class); if (config == null) { return; } for (DestinationConfig destination : config.getDestinations()) { if (StringUtils.isNotBlank(destination.getUrlPattern()) && (StringUtils.isBlank(destination.getType()) || HTTP.equalsIgnoreCase(destination.getType()))) { Pattern regex = Pattern.compile(destination.getUrlPattern()); Matcher matcher = regex.matcher(url.toExternalForm()); if (matcher.matches()) { if (LOG.isDebugEnabled()) { LOG.debug(MessageFormat.format("matched URL {0} with destination ''{1}''", url.toExternalForm(), destination.getId())); } setCredentials(client, destination); break; } } } } private void setCredentials(DefaultHttpClient client, DestinationConfig destination) { if (StringUtils.isNotBlank(destination.getUser())) { String authentication = destination.getAuthentication(); if ("basic".equalsIgnoreCase(authentication)) { //$NON-NLS-1$ CredentialsProvider credsProvider = new BasicCredentialsProvider(); credsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(destination.getUser(), destination.getPassword())); client.setCredentialsProvider(credsProvider); } else if (StringUtils.isNotBlank(authentication)) { LOG.warn(MessageFormat.format("Authentication method ''{1}'' is not supported", authentication)); } } } private void setProxy(HttpClient client, URL url) { if (isLocalDomain(url)) { return; } String protocol = url.getProtocol(); // use the system properties as default String defaultProxyHost = System.getProperty(HTTP_PROXY_HOST); String defaultProxyPort = System.getProperty(HTTP_PROXY_PORT); String proxyHost = HTTPS.equals(protocol) ? System.getProperty(HTTPS_PROXY_HOST, defaultProxyHost) : defaultProxyHost; int proxyPort = NumberUtils.toInt(HTTPS.equals(protocol) ? System.getProperty(HTTPS_PROXY_PORT, defaultProxyPort) : defaultProxyPort); String nonProxyHosts = System.getProperty(NON_PROXY_HOSTS, StringUtils.EMPTY); // allow to overwrite the system properties with configuration /api/config/proxy if (configService != null) { ProxyConfig proxyConfig = configService.readConfiguration(ProxyConfig.class); if (proxyConfig != null) { String defaultConfigProxyHost = proxyConfig.getHost(); String defaultConfigProxyPort = proxyConfig.getPort(); String configProxyHost = HTTPS.equals(protocol) ? proxyConfig.getHostSSL() : defaultConfigProxyHost; int configProxyPort = NumberUtils.toInt(HTTPS.equals(protocol) ? proxyConfig.getPortSSL() : defaultConfigProxyPort); if (StringUtils.isNotBlank(configProxyHost) && configProxyPort > 0) { proxyHost = configProxyHost; proxyPort = configProxyPort; } String configNonProxyHosts = proxyConfig.getNonProxyHosts(); if (StringUtils.isNotBlank(configNonProxyHosts)) { nonProxyHosts = configNonProxyHosts; } } } // sanitize the nonProxyHost pattern (remove whitespace etc.) if (StringUtils.isNotBlank(nonProxyHosts)) { nonProxyHosts = StringUtils.replaceEach(StringUtils.deleteWhitespace(nonProxyHosts), RE_SEARCH, RE_REPLACE); } if (StringUtils.isNotBlank(proxyHost) && proxyPort > 0 && !Pattern.matches(nonProxyHosts, url.getHost())) { HttpHost proxy = new HttpHost(proxyHost, proxyPort, HTTP); client.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy); } } /** * Returns <code>true</code> if the given URL belongs to the local domain, * i.e. only a host name like <tt>"myhost"</tt> instead of <tt>"myhost.example,org"</tt> * is specified. * * @param url the URL to check. */ private boolean isLocalDomain(URL url) { return url.getHost().indexOf('.') < 0; } private void initializeConnectionManager() { connectionManager = new ThreadSafeClientConnManager(); connectionManager.setMaxTotal(MAX_TOTAL_CONNECTIONS); connectionManager.setDefaultMaxPerRoute(MAX_CONNECTIONS_PER_ROUTE); SSLContext sslContext = getSSLContext(); try { sslContext.init(null, new TrustManager[]{ new AlwaysTrustX509Manager() }, null); } catch (KeyManagementException e) { // should not happen since we do not use any keystore throw new IllegalStateException("Failed to initialize SSL context", e); } SSLSocketFactory socketFactory = new SSLSocketFactory(sslContext, new AllowAllHostnamesVerifier()); SchemeRegistry schemeRegistry = connectionManager.getSchemeRegistry(); schemeRegistry.register(new Scheme(HTTPS, 443, socketFactory)); } private SSLContext getSSLContext() { SSLContext sslContext = null; for (int i = 0; i < SSL_PROTOCOLS.length && sslContext == null; ++i) { try { sslContext = SSLContext.getInstance(SSL_PROTOCOLS[i]); } catch (NoSuchAlgorithmException e) { LOG.debug(MessageFormat.format("SSL protocol {0} is not supported by the platform", SSL_PROTOCOLS[i])); } } if (sslContext == null) { throw new IllegalStateException("Platform does not support a suitable SSL protocol"); } return sslContext; } }