blob: f31345bdfb96a83e7aab5568be03c300510feafe [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009, 2013 Tasktop Technologies and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* https://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*
* Tasktop Technologies - initial API and implementation
*******************************************************************************/
package org.eclipse.mylyn.internal.discovery.core.model;
import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.eclipse.core.internal.registry.ExtensionRegistry;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IContributor;
import org.eclipse.core.runtime.IExtension;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.IExtensionRegistry;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.mylyn.commons.core.StatusHandler;
import org.eclipse.mylyn.internal.discovery.core.DiscoveryCore;
import org.eclipse.mylyn.internal.discovery.core.model.Directory.Entry;
import org.eclipse.mylyn.internal.discovery.core.util.WebUtil;
import org.eclipse.mylyn.internal.discovery.core.util.WebUtil.TextContentProcessor;
import org.eclipse.osgi.util.NLS;
/**
* A discovery strategy that downloads a simple directory of remote jars. The directory is first downloaded, then each
* remote jar is downloaded.
*
* @author David Green
*/
@SuppressWarnings("restriction")
public class RemoteBundleDiscoveryStrategy extends BundleDiscoveryStrategy {
private String directoryUrl;
private DiscoveryRegistryStrategy registryStrategy;
private File temporaryStorage;
private int maxDiscoveryJarDownloadAttempts = 1;
@Override
public void performDiscovery(IProgressMonitor monitor) throws CoreException {
if (connectors == null || categories == null || directoryUrl == null) {
throw new IllegalStateException();
}
if (registryStrategy != null) {
throw new IllegalStateException();
}
final int totalTicks = 100000;
final int ticksTenPercent = totalTicks / 10;
monitor.beginTask(Messages.RemoteBundleDiscoveryStrategy_task_remote_discovery, totalTicks);
try {
File registryCacheFolder;
try {
if (temporaryStorage != null && temporaryStorage.exists()) {
delete(temporaryStorage);
}
temporaryStorage = File.createTempFile(RemoteBundleDiscoveryStrategy.class.getSimpleName(), ".tmp"); //$NON-NLS-1$
temporaryStorage.delete();
if (!temporaryStorage.mkdirs()) {
throw new IOException();
}
registryCacheFolder = new File(temporaryStorage, ".rcache"); //$NON-NLS-1$
if (!registryCacheFolder.mkdirs()) {
throw new IOException();
}
} catch (IOException e) {
throw new CoreException(new Status(IStatus.ERROR, DiscoveryCore.ID_PLUGIN,
Messages.RemoteBundleDiscoveryStrategy_io_failure_temp_storage, e));
}
if (monitor.isCanceled()) {
return;
}
Directory directory;
try {
final Directory[] temp = new Directory[1];
final URI uri = new URI(directoryUrl);
WebUtil.readResource(uri, new TextContentProcessor() {
public void process(Reader reader) throws IOException {
DirectoryParser parser = new DirectoryParser();
parser.setBaseUri(uri);
temp[0] = parser.parse(reader);
}
}, new SubProgressMonitor(monitor, ticksTenPercent));
directory = temp[0];
if (directory == null) {
throw new IllegalStateException();
}
} catch (UnknownHostException e) {
throw new CoreException(new Status(IStatus.ERROR, DiscoveryCore.ID_PLUGIN, NLS.bind(
Messages.RemoteBundleDiscoveryStrategy_unknown_host_discovery_directory, e.getMessage()), e));
} catch (IOException e) {
throw new CoreException(new Status(IStatus.ERROR, DiscoveryCore.ID_PLUGIN,
Messages.RemoteBundleDiscoveryStrategy_io_failure_discovery_directory, e));
} catch (URISyntaxException e) {
throw new CoreException(new Status(IStatus.ERROR, DiscoveryCore.ID_PLUGIN,
Messages.RemoteBundleDiscoveryStrategy_io_failure_discovery_directory, e));
}
if (monitor.isCanceled()) {
return;
}
if (directory.getEntries().isEmpty()) {
throw new CoreException(new Status(IStatus.ERROR, DiscoveryCore.ID_PLUGIN,
Messages.RemoteBundleDiscoveryStrategy_empty_directory));
}
Map<File, Directory.Entry> bundleFileToDirectoryEntry = new HashMap<File, Directory.Entry>();
ExecutorService executorService = createExecutorService(directory.getEntries().size());
try {
List<Future<DownloadBundleJob>> futures = new ArrayList<Future<DownloadBundleJob>>();
// submit jobs
for (Directory.Entry entry : directory.getEntries()) {
futures.add(executorService.submit(new DownloadBundleJob(entry, monitor)));
}
int futureSize = ticksTenPercent * 4 / directory.getEntries().size();
// collect job results
for (Future<DownloadBundleJob> job : futures) {
try {
DownloadBundleJob bundleJob;
for (;;) {
try {
bundleJob = job.get(1L, TimeUnit.SECONDS);
break;
} catch (TimeoutException e) {
if (monitor.isCanceled()) {
return;
}
}
}
if (bundleJob.file != null) {
bundleFileToDirectoryEntry.put(bundleJob.file, bundleJob.entry);
}
monitor.worked(futureSize);
} catch (ExecutionException e) {
Throwable cause = e.getCause();
if (cause instanceof OperationCanceledException) {
monitor.setCanceled(true);
return;
}
IStatus status;
if (cause instanceof CoreException) {
status = ((CoreException) cause).getStatus();
} else {
status = new Status(IStatus.ERROR, DiscoveryCore.ID_PLUGIN,
Messages.RemoteBundleDiscoveryStrategy_unexpectedError, cause);
}
// log errors but continue on
StatusHandler.log(status);
} catch (InterruptedException e) {
monitor.setCanceled(true);
return;
}
}
} finally {
executorService.shutdownNow();
}
try {
registryStrategy = new DiscoveryRegistryStrategy(new File[] { registryCacheFolder },
new boolean[] { false }, this);
registryStrategy.setBundles(bundleFileToDirectoryEntry);
IExtensionRegistry extensionRegistry = new ExtensionRegistry(registryStrategy, this, this);
try {
IExtensionPoint extensionPoint = extensionRegistry.getExtensionPoint(ConnectorDiscoveryExtensionReader.EXTENSION_POINT_ID);
if (extensionPoint != null) {
IExtension[] extensions = extensionPoint.getExtensions();
if (extensions.length > 0) {
processExtensions(new SubProgressMonitor(monitor, ticksTenPercent * 3), extensions);
}
}
} finally {
extensionRegistry.stop(this);
}
} finally {
registryStrategy = null;
}
} finally {
monitor.done();
}
}
private class DownloadBundleJob implements Callable<DownloadBundleJob> {
private final IProgressMonitor monitor;
private final Entry entry;
private File file;
public DownloadBundleJob(Entry entry, IProgressMonitor monitor) {
this.entry = entry;
this.monitor = monitor;
}
public DownloadBundleJob call() {
String bundleUrl = entry.getLocation();
for (int attemptCount = 0; attemptCount < maxDiscoveryJarDownloadAttempts; ++attemptCount) {
try {
if (!bundleUrl.startsWith("http://") && !bundleUrl.startsWith("https://")) { //$NON-NLS-1$//$NON-NLS-2$
StatusHandler.log(new Status(IStatus.WARNING, DiscoveryCore.ID_PLUGIN, NLS.bind(
Messages.RemoteBundleDiscoveryStrategy_unrecognized_discovery_url, bundleUrl)));
continue;
}
File target = File.createTempFile(WebUtil.getFileNameFor(bundleUrl) + "_", ".jar", temporaryStorage); //$NON-NLS-1$ //$NON-NLS-2$
if (monitor.isCanceled()) {
break;
}
try {
WebUtil.download(new URI(bundleUrl), target, new NullProgressMonitor() {
@Override
public boolean isCanceled() {
return super.isCanceled() || monitor.isCanceled();
}
}/*don't use sub progress monitor here*/);
} catch (URISyntaxException e) {
}
file = target;
} catch (IOException e) {
StatusHandler.log(new Status(IStatus.ERROR, DiscoveryCore.ID_PLUGIN, NLS.bind(
Messages.RemoteBundleDiscoveryStrategy_cannot_download_bundle, bundleUrl, e.getMessage()),
e));
if (isUnknownHostException(e)) {
break;
}
}
}
return this;
}
}
private ExecutorService createExecutorService(int size) {
final int maxThreads = 4;
return Executors.newFixedThreadPool(Math.min(size, maxThreads));
}
/**
* walk the exception chain to determine if the given exception or any of its underlying causes are an
* {@link UnknownHostException}.
*
* @return true if the exception or one of its causes are {@link UnknownHostException}.
*/
private boolean isUnknownHostException(Throwable t) {
while (t != null) {
if (t instanceof UnknownHostException) {
return true;
}
Throwable t2 = t.getCause();
if (t2 == t) {
break;
}
t = t2;
}
return false;
}
private void delete(File file) {
if (file.exists()) {
if (file.isDirectory()) {
File[] children = file.listFiles();
if (children != null) {
for (File child : children) {
delete(child);
}
}
}
if (!file.delete()) {
// fail quietly
}
}
}
@Override
public void dispose() {
super.dispose();
if (temporaryStorage != null) {
delete(temporaryStorage);
}
}
public String getDirectoryUrl() {
return directoryUrl;
}
public void setDirectoryUrl(String directoryUrl) {
this.directoryUrl = directoryUrl;
}
@Override
protected AbstractDiscoverySource computeDiscoverySource(IContributor contributor) {
Entry directoryEntry = registryStrategy.getDirectoryEntry(contributor);
Policy policy = new Policy(directoryEntry.isPermitCategories());
JarDiscoverySource discoverySource = new JarDiscoverySource(contributor.getName(),
registryStrategy.getJarFile(contributor));
discoverySource.setPolicy(policy);
return discoverySource;
}
/**
* indicate how many times discovyer jar downloads should be attempted
*/
public int getMaxDiscoveryJarDownloadAttempts() {
return maxDiscoveryJarDownloadAttempts;
}
/**
* indicate how many times discovyer jar downloads should be attempted
*
* @param maxDiscoveryJarDownloadAttempts
* a number >= 1
*/
public void setMaxDiscoveryJarDownloadAttempts(int maxDiscoveryJarDownloadAttempts) {
if (maxDiscoveryJarDownloadAttempts < 1 || maxDiscoveryJarDownloadAttempts > 2) {
throw new IllegalArgumentException();
}
this.maxDiscoveryJarDownloadAttempts = maxDiscoveryJarDownloadAttempts;
}
}