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