diff options
author | Pascal Rapicault | 2011-01-18 20:26:59 +0000 |
---|---|---|
committer | Pascal Rapicault | 2011-01-18 20:26:59 +0000 |
commit | 7f522e530cf0d6f3b28c98c202cb5c58fa9c27bb (patch) | |
tree | 434157af47bbd10d631c5c36ad24e6b643d4a595 /bundles/org.eclipse.equinox.p2.discovery.compatibility | |
parent | f15fa1c97659feb2651680e82466dbad24bc22fa (diff) | |
download | rt.equinox.p2-7f522e530cf0d6f3b28c98c202cb5c58fa9c27bb.tar.gz rt.equinox.p2-7f522e530cf0d6f3b28c98c202cb5c58fa9c27bb.tar.xz rt.equinox.p2-7f522e530cf0d6f3b28c98c202cb5c58fa9c27bb.zip |
[discovery] Cache jars downloaded by RemoteBundleDiscoveryStrategyv20110118
Diffstat (limited to 'bundles/org.eclipse.equinox.p2.discovery.compatibility')
7 files changed, 327 insertions, 13 deletions
diff --git a/bundles/org.eclipse.equinox.p2.discovery.compatibility/META-INF/MANIFEST.MF b/bundles/org.eclipse.equinox.p2.discovery.compatibility/META-INF/MANIFEST.MF index ae53b85ea..43134b1e2 100644 --- a/bundles/org.eclipse.equinox.p2.discovery.compatibility/META-INF/MANIFEST.MF +++ b/bundles/org.eclipse.equinox.p2.discovery.compatibility/META-INF/MANIFEST.MF @@ -13,3 +13,5 @@ Require-Bundle: org.eclipse.core.runtime;bundle-version="3.3.0", Export-Package: org.eclipse.equinox.internal.p2.discovery.compatibility;x-friends:="org.eclipse.equinox.p2.ui.discovery", org.eclipse.equinox.internal.p2.discovery.compatibility.util;x-friends:="org.eclipse.equinox.p2.ui.discovery" Bundle-Localization: plugin +Bundle-Activator: org.eclipse.equinox.internal.p2.discovery.compatibility.Activator +Bundle-ActivationPolicy: lazy diff --git a/bundles/org.eclipse.equinox.p2.discovery.compatibility/src/org/eclipse/equinox/internal/p2/discovery/compatibility/Activator.java b/bundles/org.eclipse.equinox.p2.discovery.compatibility/src/org/eclipse/equinox/internal/p2/discovery/compatibility/Activator.java new file mode 100644 index 000000000..6aa66026f --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.discovery.compatibility/src/org/eclipse/equinox/internal/p2/discovery/compatibility/Activator.java @@ -0,0 +1,47 @@ +/******************************************************************************* + * Copyright (c) 2010 Sonatype, Inc. + * 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: + * Sonatype, Inc. - initial API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.internal.p2.discovery.compatibility; + +import org.eclipse.core.runtime.Plugin; +import org.eclipse.equinox.internal.p2.discovery.compatibility.util.CacheManager; +import org.eclipse.equinox.internal.p2.transport.ecf.RepositoryTransport; +import org.osgi.framework.BundleContext; + +public class Activator extends Plugin { + + private static Activator plugin; + + private CacheManager manager; + + public static final String ID = "org.eclipse.equinox.p2.discovery.compatibility"; //$NON-NLS-1$ + + public void start(BundleContext context) throws Exception { + super.start(context); + plugin = this; + } + + public void stop(BundleContext context) throws Exception { + super.stop(context); + plugin = null; + } + + public static Activator getDefault() { + return plugin; + } + + public synchronized CacheManager getCacheManager() { + if (manager == null) { + manager = new CacheManager(new RepositoryTransport()); + } + return manager; + } +} diff --git a/bundles/org.eclipse.equinox.p2.discovery.compatibility/src/org/eclipse/equinox/internal/p2/discovery/compatibility/Messages.java b/bundles/org.eclipse.equinox.p2.discovery.compatibility/src/org/eclipse/equinox/internal/p2/discovery/compatibility/Messages.java index 3e334b23c..5626eaa6e 100644 --- a/bundles/org.eclipse.equinox.p2.discovery.compatibility/src/org/eclipse/equinox/internal/p2/discovery/compatibility/Messages.java +++ b/bundles/org.eclipse.equinox.p2.discovery.compatibility/src/org/eclipse/equinox/internal/p2/discovery/compatibility/Messages.java @@ -7,6 +7,7 @@ * * Contributors: * Tasktop Technologies - initial API and implementation + * Sonatype, Inc. - added caching support *******************************************************************************/ package org.eclipse.equinox.internal.p2.discovery.compatibility; @@ -16,7 +17,7 @@ import org.eclipse.osgi.util.NLS; /** * @author David Green */ -class Messages extends NLS { +public class Messages extends NLS { private static final String BUNDLE_NAME = "org.eclipse.equinox.internal.p2.discovery.compatibility.messages"; //$NON-NLS-1$ @@ -30,6 +31,14 @@ class Messages extends NLS { public static String BundleDiscoveryStrategy_unexpected_element; + public static String CacheManager_AuthenticationFaileFor_0; + + public static String CacheManager_FailedCommunication_0; + + public static String CacheManager_Neither_0_nor_1_found; + + public static String CacheManage_ErrorRenamingCache; + public static String ConnectorDiscoveryExtensionReader_Documents; public static String ConnectorDiscoveryExtensionReader_Tasks; @@ -74,6 +83,8 @@ class Messages extends NLS { public static String SiteVerifier_Verify_Job_Label; + public static String TransportUtil_InternalError; + static { // initialize resource bundle NLS.initializeMessages(BUNDLE_NAME, Messages.class); diff --git a/bundles/org.eclipse.equinox.p2.discovery.compatibility/src/org/eclipse/equinox/internal/p2/discovery/compatibility/RemoteBundleDiscoveryStrategy.java b/bundles/org.eclipse.equinox.p2.discovery.compatibility/src/org/eclipse/equinox/internal/p2/discovery/compatibility/RemoteBundleDiscoveryStrategy.java index ddafba1f3..707aa9471 100644 --- a/bundles/org.eclipse.equinox.p2.discovery.compatibility/src/org/eclipse/equinox/internal/p2/discovery/compatibility/RemoteBundleDiscoveryStrategy.java +++ b/bundles/org.eclipse.equinox.p2.discovery.compatibility/src/org/eclipse/equinox/internal/p2/discovery/compatibility/RemoteBundleDiscoveryStrategy.java @@ -7,6 +7,7 @@ * * Contributors: * Tasktop Technologies - initial API and implementation + * Sonatype, Inc. - added caching support *******************************************************************************/ package org.eclipse.equinox.internal.p2.discovery.compatibility; @@ -192,10 +193,6 @@ public class RemoteBundleDiscoveryStrategy extends BundleDiscoveryStrategy { 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$ - LogHelper.log(new Status(IStatus.WARNING, DiscoveryCore.ID_PLUGIN, NLS.bind(Messages.RemoteBundleDiscoveryStrategy_unrecognized_discovery_url, bundleUrl))); - continue; - } String lastPathElement = bundleUrl.lastIndexOf('/') == -1 ? bundleUrl : bundleUrl.substring(bundleUrl.lastIndexOf('/')); File target = File.createTempFile(lastPathElement.replaceAll("^[a-zA-Z0-9_.]", "_") + "_", ".jar", temporaryStorage); //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$//$NON-NLS-4$ @@ -217,6 +214,8 @@ public class RemoteBundleDiscoveryStrategy extends BundleDiscoveryStrategy { if (isUnknownHostException(e)) { break; } + } catch (CoreException e) { + LogHelper.log(new Status(IStatus.ERROR, DiscoveryCore.ID_PLUGIN, NLS.bind(Messages.RemoteBundleDiscoveryStrategy_cannot_download_bundle, bundleUrl, e.getMessage()), e)); } } return this; diff --git a/bundles/org.eclipse.equinox.p2.discovery.compatibility/src/org/eclipse/equinox/internal/p2/discovery/compatibility/messages.properties b/bundles/org.eclipse.equinox.p2.discovery.compatibility/src/org/eclipse/equinox/internal/p2/discovery/compatibility/messages.properties index d4c1a86b4..f20bfad87 100644 --- a/bundles/org.eclipse.equinox.p2.discovery.compatibility/src/org/eclipse/equinox/internal/p2/discovery/compatibility/messages.properties +++ b/bundles/org.eclipse.equinox.p2.discovery.compatibility/src/org/eclipse/equinox/internal/p2/discovery/compatibility/messages.properties @@ -7,12 +7,17 @@ # # Contributors: # Tasktop Technologies - initial API and implementation +# Sonatype, Inc. - added caching support ############################################################################### BundleDiscoveryStrategy_3={0}: {1} BundleDiscoveryStrategy_categoryDisallowed=Cannot create category ''{0}'' with id ''{1}'' from {2}: disallowed BundleDiscoveryStrategy_task_loading_local_extensions=Loading local extensions BundleDiscoveryStrategy_task_processing_extensions=Processing extensions BundleDiscoveryStrategy_unexpected_element=unexpected element ''{0}'' +CacheManager_Neither_0_nor_1_found=Neither {0} nor {1} found. +CacheManager_AuthenticationFaileFor_0=Authentication failed for {0}. +CacheManager_FailedCommunication_0=Communication with {0} failed. +CacheManage_ErrorRenamingCache=An error occurred while downloading {0}. The cache file {1} could not be renamed to {2}. ConnectorDiscoveryExtensionReader_Documents=Documents ConnectorDiscoveryExtensionReader_Tasks=Tasks ConnectorDiscoveryExtensionReader_unexpected_element_icon=Unexpected element icon @@ -35,3 +40,4 @@ RemoteBundleDiscoveryStrategy_unrecognized_discovery_url=Unrecognized discovery SiteVerifier_Error_with_cause={0}: {1} SiteVerifier_Unexpected_Error=Unexpected error while verifying site availability SiteVerifier_Verify_Job_Label=Verifying availability +TransportUtil_InternalError=Internal Error diff --git a/bundles/org.eclipse.equinox.p2.discovery.compatibility/src/org/eclipse/equinox/internal/p2/discovery/compatibility/util/CacheManager.java b/bundles/org.eclipse.equinox.p2.discovery.compatibility/src/org/eclipse/equinox/internal/p2/discovery/compatibility/util/CacheManager.java new file mode 100644 index 000000000..fba3ba1ef --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.discovery.compatibility/src/org/eclipse/equinox/internal/p2/discovery/compatibility/util/CacheManager.java @@ -0,0 +1,219 @@ +/******************************************************************************* + * Copyright (c) 2008, 2009 IBM Corporation 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: + * IBM Corporation - initial API and implementation + * Cloudsmith Inc - additional implementation + * Sonatype, Inc. - additional implementation and p2 discovery support + *******************************************************************************/ +package org.eclipse.equinox.internal.p2.discovery.compatibility.util; + +import java.io.*; +import java.net.URI; +import org.eclipse.core.runtime.*; +import org.eclipse.equinox.internal.p2.core.helpers.LogHelper; +import org.eclipse.equinox.internal.p2.discovery.compatibility.Activator; +import org.eclipse.equinox.internal.p2.discovery.compatibility.Messages; +import org.eclipse.equinox.internal.p2.repository.AuthenticationFailedException; +import org.eclipse.equinox.internal.p2.repository.Transport; +import org.eclipse.equinox.internal.provisional.p2.repository.IStateful; +import org.eclipse.equinox.p2.core.ProvisionException; +import org.eclipse.osgi.util.NLS; + +/** + * A class to manage discovery cache files. Creating the cache files will place + * the file in the plugin state location in a cache directory. + */ +@SuppressWarnings("restriction") +public class CacheManager { + + private static final String PREFIX = "discovery"; //$NON-NLS-1$ + + private static final String DOWNLOADING = "downloading"; //$NON-NLS-1$ + + private final Transport transport; + + /** + * IStateful implementation of BufferedOutputStream. Class is used to get the status from + * a download operation. + */ + private static class StatefulStream extends BufferedOutputStream implements IStateful { + private IStatus status; + + public StatefulStream(OutputStream stream) { + super(stream); + } + + public IStatus getStatus() { + + return status; + } + + public void setStatus(IStatus aStatus) { + status = aStatus; + } + + } + + public CacheManager(Transport transport) { + this.transport = transport; + } + + /** + * Returns a hash of the location. + */ + private int computeHash(URI location) { + return location.hashCode(); + } + + /** + * Returns a local cache file with the contents of the given remote location, + * or <code>null</code> if a local cache could not be created. + * + * @param location the remote location to be cached + * @param monitor a progress monitor + * @return A {@link File} object pointing to the cache file or <code>null</code> + * @throws FileNotFoundException if neither jar nor xml index file exists at given location + * @throws AuthenticationFailedException if jar not available and xml causes authentication fail + * @throws IOException on general IO errors + * @throws ProvisionException on any error (e.g. user cancellation, unknown host, malformed address, connection refused, etc.) + * @throws OperationCanceledException - if user cancelled + */ + public File createCache(URI location, IProgressMonitor monitor) throws IOException, ProvisionException { + + SubMonitor submonitor = SubMonitor.convert(monitor, 1000); + try { + File cacheFile = getCache(location); + + boolean stale = true; + long lastModified = 0L; + + if (cacheFile != null) { + lastModified = cacheFile.lastModified(); + } + // get last modified on jar + long lastModifiedRemote = 0L; + // bug 269588 - server may return 0 when file exists, so extra flag is needed + try { + lastModifiedRemote = transport.getLastModified(location, submonitor.newChild(1)); + if (lastModifiedRemote <= 0) + LogHelper.log(new Status(IStatus.WARNING, Activator.ID, "Server returned lastModified <= 0 for " + location)); //$NON-NLS-1$ + } catch (AuthenticationFailedException e) { + // it is not meaningful to continue - the credentials are for the server + // do not pass the exception - it gives no additional meaningful user information + throw new ProvisionException(new Status(IStatus.ERROR, Activator.ID, ProvisionException.REPOSITORY_FAILED_AUTHENTICATION, NLS.bind(Messages.CacheManager_AuthenticationFaileFor_0, location), null)); + } catch (CoreException e) { + throw new ProvisionException(e.getStatus()); + } catch (OperationCanceledException e) { + // must pass this on + throw e; + } + if (submonitor.isCanceled()) + throw new OperationCanceledException(); + stale = lastModifiedRemote > lastModified || lastModifiedRemote <= 0; + + if (!stale) + return cacheFile; + + // The cache is stale or missing, so we need to update it from the remote location + cacheFile = getCacheFile(location); + updateCache(cacheFile, location, lastModifiedRemote, submonitor); + return cacheFile; + } finally { + submonitor.done(); + } + } + + /** + * Deletes the local cache file(s) for the given location + * @param location + */ + void deleteCache(URI location) { + File cacheFile = getCache(location); + // delete the cache file if it exists + safeDelete(cacheFile); + // delete a resumable download if it exists + safeDelete(new File(new File(cacheFile.getParentFile(), DOWNLOADING), cacheFile.getName())); + } + + /** + * Determines the local file paths of the locations potential cache file. + * @param location The location to compute the cache for + * @param PREFIX The prefix to use for this location + * @return A {@link File} array with the cache files for JAR and XML extensions. + */ + private File getCache(URI location) { + File cacheFile = getCacheFile(location); + return cacheFile.exists() ? cacheFile : null; + } + + private File getCacheFile(URI location) { + return new File(getCacheDirectory(), PREFIX + computeHash(location)); + } + + /** + * Returns the file corresponding to the data area to be used by the cache manager. + */ + protected File getCacheDirectory() { + return Activator.getDefault().getStateLocation().append("cache").toFile(); //$NON-NLS-1$ + } + + private boolean safeDelete(File file) { + if (file.exists()) { + if (!file.delete()) { + file.deleteOnExit(); + return true; + } + } + return false; + } + + protected void updateCache(File cacheFile, URI remoteFile, long lastModifiedRemote, SubMonitor submonitor) throws FileNotFoundException, IOException, ProvisionException { + cacheFile.getParentFile().mkdirs(); + File downloadDir = new File(cacheFile.getParentFile(), DOWNLOADING); + if (!downloadDir.exists()) + downloadDir.mkdir(); + File tempFile = new File(downloadDir, cacheFile.getName()); + // Ensure that the file from a previous download attempt is removed + if (tempFile.exists()) + safeDelete(tempFile); + + tempFile.createNewFile(); + + StatefulStream stream = null; + try { + stream = new StatefulStream(new FileOutputStream(tempFile)); + } catch (Exception e) { + throw new ProvisionException(new Status(IStatus.ERROR, Activator.ID, e.getMessage(), e)); + } + IStatus result = null; + try { + submonitor.setWorkRemaining(1000); + result = transport.download(remoteFile, stream, submonitor.newChild(1000)); + } catch (OperationCanceledException e) { + // need to pick up the status - a new operation canceled exception is thrown at the end + // as status will be CANCEL. + result = stream.getStatus(); + } finally { + stream.close(); + // If there was any problem fetching the file, delete the temp file + if (result == null || !result.isOK()) + safeDelete(tempFile); + } + if (result.isOK()) { + if (cacheFile.exists()) + safeDelete(cacheFile); + if (tempFile.renameTo(cacheFile)) + return; + result = new Status(IStatus.ERROR, Activator.ID, NLS.bind(Messages.CacheManage_ErrorRenamingCache, new Object[] {remoteFile.toString(), tempFile.getAbsolutePath(), cacheFile.getAbsolutePath()})); + } + + if (result.getSeverity() == IStatus.CANCEL || submonitor.isCanceled()) + throw new OperationCanceledException(); + throw new ProvisionException(result); + } +} diff --git a/bundles/org.eclipse.equinox.p2.discovery.compatibility/src/org/eclipse/equinox/internal/p2/discovery/compatibility/util/TransportUtil.java b/bundles/org.eclipse.equinox.p2.discovery.compatibility/src/org/eclipse/equinox/internal/p2/discovery/compatibility/util/TransportUtil.java index a0ccbd008..be0fdf1d0 100644 --- a/bundles/org.eclipse.equinox.p2.discovery.compatibility/src/org/eclipse/equinox/internal/p2/discovery/compatibility/util/TransportUtil.java +++ b/bundles/org.eclipse.equinox.p2.discovery.compatibility/src/org/eclipse/equinox/internal/p2/discovery/compatibility/util/TransportUtil.java @@ -7,15 +7,16 @@ * * Contributors: * Tasktop Technologies - initial API and implementation - * Sonatype Inc. - transport split + * Sonatype, Inc. - transport split and caching support *******************************************************************************/ package org.eclipse.equinox.internal.p2.discovery.compatibility.util; import java.io.*; import java.net.URI; import java.util.List; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.*; +import org.eclipse.equinox.internal.p2.discovery.compatibility.Activator; +import org.eclipse.equinox.internal.p2.discovery.compatibility.Messages; import org.eclipse.equinox.internal.p2.repository.AuthenticationFailedException; import org.eclipse.equinox.internal.p2.transport.ecf.RepositoryTransport; @@ -48,13 +49,37 @@ public class TransportUtil { * the monitor * @throws IOException * if a network or IO problem occurs + * @throws CoreException */ - public static void downloadResource(URI location, File target, IProgressMonitor monitor) throws IOException { - OutputStream out = new BufferedOutputStream(new FileOutputStream(target)); + public static void downloadResource(URI location, File target, IProgressMonitor monitor) throws IOException, CoreException { + CacheManager cm = Activator.getDefault().getCacheManager(); + File cacheFile = cm.createCache(location, monitor); + if (cacheFile == null) { + throw new CoreException(new Status(IStatus.ERROR, Activator.ID, Messages.TransportUtil_InternalError)); + } + copyStream(new BufferedInputStream(new FileInputStream(cacheFile)), true, new BufferedOutputStream(new FileOutputStream(target)), true); + } + + public static int copyStream(InputStream in, boolean closeIn, OutputStream out, boolean closeOut) throws IOException { try { - new RepositoryTransport().download(location, out, monitor); + int written = 0; + byte[] buffer = new byte[16 * 1024]; + int len; + while ((len = in.read(buffer)) != -1) { + out.write(buffer, 0, len); + written += len; + } + return written; } finally { - out.close(); + try { + if (closeIn) { + in.close(); + } + } finally { + if (closeOut) { + out.close(); + } + } } } @@ -72,7 +97,12 @@ public class TransportUtil { * @throws CoreException */ public static void readResource(URI location, TextContentProcessor processor, IProgressMonitor monitor) throws IOException, CoreException { - InputStream in = new RepositoryTransport().stream(location, monitor); + CacheManager cm = Activator.getDefault().getCacheManager(); + File cacheFile = cm.createCache(location, monitor); + if (cacheFile == null) { + throw new CoreException(new Status(IStatus.ERROR, Activator.ID, Messages.TransportUtil_InternalError)); + } + InputStream in = new BufferedInputStream(new FileInputStream(cacheFile)); try { // FIXME how can the charset be determined? BufferedReader reader = new BufferedReader(new InputStreamReader(in, "UTF-8")); //$NON-NLS-1$ |