blob: da3a9d5603e1201b1e4a0252d25dc78d4f713bf8 [file] [log] [blame]
/****************************************************************************
* Copyright (c) 2021 IBM, Composent Inc. and others.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* Contributors:
* Chris Aniszczyk - initial API and implementation
* Yatta Solutions - HttpClient 4.5 implementation
*
* SPDX-License-Identifier: EPL-2.0
*****************************************************************************/
package org.eclipse.ecf.internal.provider.filetransfer.httpclient5;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import javax.net.ssl.SSLSocketFactory;
import org.apache.hc.client5.http.classic.HttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.ecf.core.util.ECFRuntimeException;
import org.eclipse.ecf.core.util.LogHelper;
import org.eclipse.ecf.core.util.Trace;
import org.eclipse.ecf.filetransfer.service.IRemoteFileSystemBrowser;
import org.eclipse.ecf.filetransfer.service.IRetrieveFileTransfer;
import org.eclipse.ecf.internal.provider.filetransfer.DebugOptions;
import org.eclipse.ecf.internal.provider.filetransfer.httpclient5.ECFHttpClientFactory.ModifierRunner;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.service.log.LogService;
import org.osgi.util.tracker.ServiceTracker;
import org.osgi.util.tracker.ServiceTrackerCustomizer;
/**
* The activator class controls the plug-in life cycle
*/
@SuppressWarnings("restriction")
public class Activator implements BundleActivator {
private static final class ScopedHttpClientCustomizer implements ServiceTrackerCustomizer<HttpClient, CloseableHttpClient> {
private final String neededScope;
private final BundleContext context;
public ScopedHttpClientCustomizer(BundleContext context, String neededScope) {
this.context = context;
this.neededScope = neededScope;
}
@Override
public CloseableHttpClient addingService(ServiceReference<HttpClient> reference) {
if (!hasScope(reference, neededScope)) {
return null;
}
HttpClient service = context.getService(reference);
if (service instanceof CloseableHttpClient) {
return (CloseableHttpClient) service;
}
context.ungetService(reference);
return null;
}
private boolean hasScope(ServiceReference<HttpClient> reference, String neededScope) {
Object scopeProperty = reference.getProperty("http.client.scope"); //$NON-NLS-1$
if (scopeProperty == null || !(scopeProperty instanceof String)) {
return false;
}
String[] scopes = ((String) scopeProperty).split("\\s*,\\s*"); //$NON-NLS-1$
for (String scope : scopes) {
if (neededScope.equals(scope) || (scope.endsWith("*") && neededScope.startsWith(scope.substring(0, scope.length() - 1)))) { //$NON-NLS-1$
return true;
}
}
return false;
}
@Override
public void modifiedService(ServiceReference<HttpClient> reference, CloseableHttpClient service) {
if (!hasScope(reference, neededScope)) {
context.ungetService(reference);
}
}
@Override
public void removedService(ServiceReference<HttpClient> reference, CloseableHttpClient service) {
context.ungetService(reference);
}
}
// The plug-in ID
public static final String PLUGIN_ID = "org.eclipse.ecf.provider.filetransfer.httpclient45"; //$NON-NLS-1$
public static final String USE_SHARED_CLIENT = PLUGIN_ID + ".sharedClient"; //$NON-NLS-1$
private static final String USE_SHARED_CLIENT_DEFAULT = "true"; //$NON-NLS-1$
// The shared instance
private static Activator plugin;
private BundleContext context = null;
private ServiceTracker<LogService, LogService> logServiceTracker = null;
private ServiceTracker<SSLSocketFactory, SSLSocketFactory> sslSocketFactoryTracker;
private ServiceTracker<INTLMProxyHandler, INTLMProxyHandler> ntlmProxyHandlerTracker;
private ServiceTracker<IHttpClientFactory, IHttpClientFactory> httpClientFactoryTracker;
private ServiceTracker<HttpClient, CloseableHttpClient> browseClientTracker;
private ServiceTracker<HttpClient, CloseableHttpClient> retrieveClientTracker;
private boolean useSharedClient;
/**
* The constructor
*/
public Activator() {
//
}
public BundleContext getContext() {
return context;
}
@Override
public void start(BundleContext ctxt) throws Exception {
plugin = this;
this.context = ctxt;
useSharedClient = Boolean.parseBoolean(System.getProperty(USE_SHARED_CLIENT, USE_SHARED_CLIENT_DEFAULT));
applyDebugOptions(ctxt);
}
private void applyDebugOptions(BundleContext ctxt) {
ServiceReference<org.eclipse.osgi.service.debug.DebugOptions> debugRef = ctxt.getServiceReference(org.eclipse.osgi.service.debug.DebugOptions.class);
org.eclipse.osgi.service.debug.DebugOptions debugOptions = debugRef == null ? null : ctxt.getService(debugRef);
if (debugOptions == null) {
return;
}
try {
if (!debugOptions.isDebugEnabled()) {
return;
}
Map<String, String> options = debugOptions.getOptions();
String ourDebugPrefix = PLUGIN_ID + "/";
String ecfDebugPrefix = "org.eclipse.ecf.provider.filetransfer.httpclient4/";
for (Map.Entry<String, String> entry : options.entrySet()) {
if (entry.getKey() != null && entry.getKey().startsWith(ourDebugPrefix)) {
String ecfOption = ecfDebugPrefix + entry.getKey().substring(ourDebugPrefix.length());
String ecfValue = options.get(ecfOption);
if (ecfValue == null) {
debugOptions.setOption(ecfOption, entry.getValue());
}
}
}
} finally {
ctxt.ungetService(debugRef);
}
}
@Override
public synchronized void stop(BundleContext ctxt) throws Exception {
if (sslSocketFactoryTracker != null) {
sslSocketFactoryTracker.close();
}
if (logServiceTracker != null) {
logServiceTracker.close();
}
if (ntlmProxyHandlerTracker != null) {
ntlmProxyHandlerTracker.close();
}
this.context = null;
plugin = null;
}
/**
* Returns the shared instance
*
* @return the shared instance
*/
public synchronized static Activator getDefault() {
if (plugin == null) {
plugin = new Activator();
}
return plugin;
}
private synchronized LogService getLogService() {
if (logServiceTracker == null) {
logServiceTracker = new ServiceTracker<LogService, LogService>(this.context, LogService.class, null);
logServiceTracker.open();
}
return logServiceTracker.getService();
}
public boolean isUseSharedClient() {
return useSharedClient;
}
public void log(IStatus status) {
LogService logService = getLogService();
if (logService != null) {
logService.log(LogHelper.getLogCode(status), LogHelper.getLogMessage(status), status.getException());
}
}
public synchronized SSLSocketFactory getSSLSocketFactory() {
if (sslSocketFactoryTracker == null) {
sslSocketFactoryTracker = new ServiceTracker<SSLSocketFactory, SSLSocketFactory>(this.context, SSLSocketFactory.class, null);
sslSocketFactoryTracker.open();
}
SSLSocketFactory service = sslSocketFactoryTracker.getService();
return service;
}
public synchronized INTLMProxyHandler getNTLMProxyHandler() {
if (ntlmProxyHandlerTracker == null) {
ntlmProxyHandlerTracker = new ServiceTracker<INTLMProxyHandler, INTLMProxyHandler>(this.context, INTLMProxyHandler.class, null);
ntlmProxyHandlerTracker.open();
}
INTLMProxyHandler service = ntlmProxyHandlerTracker.getService();
if (service == null) {
service = new DefaultNTLMProxyHandler();
}
return service;
}
public synchronized IHttpClientFactory getHttpClientFactory() {
if (httpClientFactoryTracker == null) {
httpClientFactoryTracker = new ServiceTracker<IHttpClientFactory, IHttpClientFactory>(this.context, IHttpClientFactory.class, null);
httpClientFactoryTracker.open();
}
IHttpClientFactory service = httpClientFactoryTracker.getService();
if (service == null) {
service = new ECFHttpClientFactory();
Dictionary<String, Object> serviceProperties = new Hashtable<String, Object>();
serviceProperties.put(Constants.SERVICE_RANKING, Integer.MIN_VALUE);
context.registerService(IHttpClientFactory.class, service, serviceProperties);
}
return service;
}
public synchronized CloseableHttpClient getBrowseHttpClient() {
CloseableHttpClient service;
if (isUseSharedClient()) {
if (browseClientTracker == null) {
browseClientTracker = new ServiceTracker<HttpClient, CloseableHttpClient>(context, HttpClient.class, new ScopedHttpClientCustomizer(context, IRemoteFileSystemBrowser.class.getName()));
browseClientTracker.open();
}
service = browseClientTracker.getService();
if (service == null) {
service = registerHttpClient();
}
} else {
service = getHttpClientFactory().newClient().build();
}
return service;
}
public synchronized CloseableHttpClient getRetrieveHttpClient() {
CloseableHttpClient service;
if (isUseSharedClient()) {
if (retrieveClientTracker == null) {
retrieveClientTracker = new ServiceTracker<HttpClient, CloseableHttpClient>(context, HttpClient.class, new ScopedHttpClientCustomizer(context, IRetrieveFileTransfer.class.getName()));
retrieveClientTracker.open();
}
service = retrieveClientTracker.getService();
if (service == null) {
service = registerHttpClient();
}
} else {
service = getHttpClientFactory().newClient().build();
}
return service;
}
private CloseableHttpClient registerHttpClient() {
CloseableHttpClient client = getHttpClientFactory().newClient().build();
Dictionary<String, Object> serviceProperties = new Hashtable<String, Object>();
serviceProperties.put(Constants.SERVICE_RANKING, Integer.MIN_VALUE);
serviceProperties.put("http.client.scope", "org.eclipse.ecf.filetransfer.service.*");
context.registerService(new String[] {HttpClient.class.getName(), CloseableHttpClient.class.getName()}, client, serviceProperties);
return client;
}
public static void logNoProxyWarning(Throwable e) {
Activator a = getDefault();
if (a != null) {
a.log(new Status(IStatus.WARNING, Activator.PLUGIN_ID, IStatus.ERROR, "Warning: Platform proxy API not available", e)); //$NON-NLS-1$
}
}
protected <T> T runModifiers(T value, ModifierRunner<T> modifierRunner) {
T modifiedValue = value;
List<ServiceReference<IHttpClientModifier>> orderedServices = getModifierReferences();
for (ServiceReference<IHttpClientModifier> serviceReference : orderedServices) {
IHttpClientModifier modifier = this.context.getService(serviceReference);
try {
if (modifier == null) {
continue;
}
T newValue = modifierRunner.run(modifier, modifiedValue);
if (newValue != null) {
modifiedValue = newValue;
}
} finally {
this.context.ungetService(serviceReference);
}
}
return modifiedValue;
}
private List<ServiceReference<IHttpClientModifier>> getModifierReferences() {
Collection<ServiceReference<IHttpClientModifier>> serviceReferences;
try {
serviceReferences = this.context.getServiceReferences(IHttpClientModifier.class, null);
} catch (InvalidSyntaxException e) {
// Can't happen
throw new ECFRuntimeException(e);
}
List<ServiceReference<IHttpClientModifier>> orderedServices = new ArrayList<ServiceReference<IHttpClientModifier>>(serviceReferences);
if (orderedServices.size() < 2) {
return orderedServices;
}
Collections.sort(orderedServices, new Comparator<ServiceReference<?>>() {
@Override
public int compare(ServiceReference<?> o1, ServiceReference<?> o2) {
if (o1 == o2) {
return 0;
}
int ranking1 = getServiceRanking(o1);
int ranking2 = getServiceRanking(o2);
// Lowest ranking first
return ranking1 - ranking2;
}
private int getServiceRanking(ServiceReference<?> reference) {
Object rankingValue = reference.getProperty(Constants.SERVICE_RANKING);
if (rankingValue instanceof Integer) {
return (Integer) rankingValue;
} else if (rankingValue instanceof String) {
try {
return Integer.parseInt((String) rankingValue);
} catch (NumberFormatException e) {
Trace.catching(Activator.PLUGIN_ID, DebugOptions.EXCEPTIONS_CATCHING, Activator.class, "getServiceRanking", e); //$NON-NLS-1$
}
}
return 0;
}
});
return orderedServices;
}
}