/******************************************************************************* * Copyright (c) 2009, 2013 Tasktop Technologies 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: * 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; } }