diff options
author | Pascal Rapicault | 2010-12-25 04:29:47 +0000 |
---|---|---|
committer | Pascal Rapicault | 2010-12-25 04:29:47 +0000 |
commit | 952f0261aae1ae40b11d72fc204a37dd189e3476 (patch) | |
tree | 739b2070fe483b4a64993f913456bfc90a89b3e4 /bundles/org.eclipse.equinox.p2.transport.ecf | |
parent | bb2fff87b9edef35e79fdeb17784618ee79527d4 (diff) | |
download | rt.equinox.p2-952f0261aae1ae40b11d72fc204a37dd189e3476.tar.gz rt.equinox.p2-952f0261aae1ae40b11d72fc204a37dd189e3476.tar.xz rt.equinox.p2-952f0261aae1ae40b11d72fc204a37dd189e3476.zip |
Bug 321819 - [transport] Repository transport API is tightly coupled with the implementationv20110104v20110102
Diffstat (limited to 'bundles/org.eclipse.equinox.p2.transport.ecf')
18 files changed, 1974 insertions, 0 deletions
diff --git a/bundles/org.eclipse.equinox.p2.transport.ecf/.classpath b/bundles/org.eclipse.equinox.p2.transport.ecf/.classpath new file mode 100644 index 000000000..ad32c83a7 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.transport.ecf/.classpath @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<classpath> + <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/> + <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/> + <classpathentry kind="src" path="src"/> + <classpathentry kind="output" path="bin"/> +</classpath> diff --git a/bundles/org.eclipse.equinox.p2.transport.ecf/.project b/bundles/org.eclipse.equinox.p2.transport.ecf/.project new file mode 100644 index 000000000..fb4698e85 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.transport.ecf/.project @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>org.eclipse.equinox.p2.transport.ecf</name> + <comment></comment> + <projects> + </projects> + <buildSpec> + <buildCommand> + <name>org.eclipse.jdt.core.javabuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>org.eclipse.pde.ManifestBuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>org.eclipse.pde.SchemaBuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>org.eclipse.pde.ds.core.builder</name> + <arguments> + </arguments> + </buildCommand> + </buildSpec> + <natures> + <nature>org.eclipse.pde.PluginNature</nature> + <nature>org.eclipse.jdt.core.javanature</nature> + </natures> +</projectDescription> diff --git a/bundles/org.eclipse.equinox.p2.transport.ecf/.settings/org.eclipse.jdt.core.prefs b/bundles/org.eclipse.equinox.p2.transport.ecf/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 000000000..3a159d8f0 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.transport.ecf/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,8 @@ +#Fri Dec 24 16:24:53 EST 2010 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/bundles/org.eclipse.equinox.p2.transport.ecf/.settings/org.eclipse.pde.core.prefs b/bundles/org.eclipse.equinox.p2.transport.ecf/.settings/org.eclipse.pde.core.prefs new file mode 100644 index 000000000..0f5d304a4 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.transport.ecf/.settings/org.eclipse.pde.core.prefs @@ -0,0 +1,4 @@ +#Fri Dec 24 16:24:53 EST 2010 +eclipse.preferences.version=1 +pluginProject.extensions=false +resolve.requirebundle=false diff --git a/bundles/org.eclipse.equinox.p2.transport.ecf/META-INF/MANIFEST.MF b/bundles/org.eclipse.equinox.p2.transport.ecf/META-INF/MANIFEST.MF new file mode 100644 index 000000000..e8aa00368 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.transport.ecf/META-INF/MANIFEST.MF @@ -0,0 +1,19 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: %pluginName +Bundle-SymbolicName: org.eclipse.equinox.p2.transport.ecf +Bundle-Version: 1.0.0.qualifier +Bundle-RequiredExecutionEnvironment: J2SE-1.5, + J2SE-1.4 +Require-Bundle: org.eclipse.ecf;bundle-version="3.1.0", + org.eclipse.ecf.filetransfer;bundle-version="4.0.0", + org.eclipse.ecf.provider.filetransfer;bundle-version="3.1.0", + org.eclipse.core.runtime;bundle-version="3.6.100", + org.eclipse.equinox.p2.core;bundle-version="2.0.100", + org.eclipse.equinox.p2.repository;bundle-version="2.1.0" +Service-Component: OSGI-INF/ecfTransport.xml +Bundle-Activator: org.eclipse.equinox.internal.p2.transport.ecf.Activator +Bundle-ActivationPolicy: lazy +Export-Package: org.eclipse.equinox.internal.p2.transport.ecf +Bundle-Vendor: %providerName +Bundle-Localization: plugin diff --git a/bundles/org.eclipse.equinox.p2.transport.ecf/OSGI-INF/ecfTransport.xml b/bundles/org.eclipse.equinox.p2.transport.ecf/OSGI-INF/ecfTransport.xml new file mode 100644 index 000000000..d83b000c0 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.transport.ecf/OSGI-INF/ecfTransport.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="org.eclipse.equinox.p2.transport.ecf">
+ <implementation class="org.eclipse.equinox.internal.p2.transport.ecf.ECFTransportComponent"/>
+ <service>
+ <provide interface="org.eclipse.equinox.p2.core.spi.IAgentServiceFactory"/>
+ </service>
+ <property name="p2.agent.servicename" type="String" value="org.eclipse.equinox.internal.p2.repository.Transport"/>
+</scr:component>
diff --git a/bundles/org.eclipse.equinox.p2.transport.ecf/build.properties b/bundles/org.eclipse.equinox.p2.transport.ecf/build.properties new file mode 100644 index 000000000..cfa352811 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.transport.ecf/build.properties @@ -0,0 +1,6 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + .,\ + OSGI-INF/,\ + plugin.properties diff --git a/bundles/org.eclipse.equinox.p2.transport.ecf/plugin.properties b/bundles/org.eclipse.equinox.p2.transport.ecf/plugin.properties new file mode 100644 index 000000000..4b7e42c0e --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.transport.ecf/plugin.properties @@ -0,0 +1,12 @@ +############################################################################### +# Copyright (c) 2010 Sonatype, Inc. 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: +# Sonatype, Inc. - initial API and implementation +############################################################################### +pluginName = Equinox ECF based transport for p2 +providerName = Eclipse.org - Equinox diff --git a/bundles/org.eclipse.equinox.p2.transport.ecf/pom.xml b/bundles/org.eclipse.equinox.p2.transport.ecf/pom.xml new file mode 100644 index 000000000..8325c35a4 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.transport.ecf/pom.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <modelVersion>4.0.0</modelVersion> + <parent> + <artifactId>org.eclipse.equinox.p2-parent</artifactId> + <groupId>org.eclipse</groupId> + <version>0.0.1-SNAPSHOT</version><relativePath>../org.eclipse.equinox.p2-parent</relativePath> + </parent> + <groupId>org.eclipse</groupId> + <artifactId>org.eclipse.equinox.p2.transport.ecf</artifactId> + <version>1.0.0.qualifier</version> + <packaging>eclipse-plugin</packaging> +</project> diff --git a/bundles/org.eclipse.equinox.p2.transport.ecf/src/org/eclipse/equinox/internal/p2/transport/ecf/Activator.java b/bundles/org.eclipse.equinox.p2.transport.ecf/src/org/eclipse/equinox/internal/p2/transport/ecf/Activator.java new file mode 100644 index 000000000..b21c52c03 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.transport.ecf/src/org/eclipse/equinox/internal/p2/transport/ecf/Activator.java @@ -0,0 +1,169 @@ +/******************************************************************************* + * Copyright (c) 2009 Cloudsmith Inc 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: + * Cloudsmith Inc - initial API and implementation + * IBM Corporation - ongoing development + * Genuitec - Bug 291926 + ******************************************************************************/ +package org.eclipse.equinox.internal.p2.transport.ecf; + +import org.eclipse.ecf.filetransfer.service.IRetrieveFileTransferFactory; +import org.eclipse.ecf.provider.filetransfer.IFileTransferProtocolToFactoryMapper; +import org.eclipse.equinox.internal.p2.core.helpers.ServiceHelper; +import org.osgi.framework.*; +import org.osgi.service.packageadmin.PackageAdmin; +import org.osgi.util.tracker.ServiceTracker; + +/** + * The activator class controls the plug-in life cycle. + * This activator has helper methods to get file transfer service tracker, and + * for making sure required ECF bundles are started. + */ +public class Activator implements BundleActivator { + + public static final String ID = "org.eclipse.equinox.p2.transport.ecf"; //$NON-NLS-1$ + private static final String HTTP = "http"; //$NON-NLS-1$ + private static final String HTTPS = "https"; //$NON-NLS-1$ + + private static BundleContext context; + // tracker for ECF service + private ServiceTracker<IRetrieveFileTransferFactory, IRetrieveFileTransferFactory> retrievalFactoryTracker; + + // tracker for protocolToFactoryMapperTracker + private ServiceTracker<IFileTransferProtocolToFactoryMapper, IFileTransferProtocolToFactoryMapper> protocolToFactoryMapperTracker = null; + + // The shared instance + private static Activator plugin; + + public void start(BundleContext aContext) throws Exception { + Activator.context = aContext; + Activator.plugin = this; + } + + public void stop(BundleContext aContext) throws Exception { + Activator.context = null; + Activator.plugin = null; + if (retrievalFactoryTracker != null) { + retrievalFactoryTracker.close(); + retrievalFactoryTracker = null; + } + if (protocolToFactoryMapperTracker != null) { + protocolToFactoryMapperTracker.close(); + protocolToFactoryMapperTracker = null; + } + + } + + public static BundleContext getContext() { + return Activator.context; + } + + /** + * Get singleton instance. + * + * @return the shared instance + */ + public static Activator getDefault() { + return plugin; + } + + /** + * Returns a {@link IRetrieveFileTransferFactory} using a {@link ServiceTracker} after having attempted + * to start the bundle "org.eclipse.ecf.provider.filetransfer". If something is wrong with the configuration + * this method returns null. + * @return a factory, or null, if configuration is incorrect + */ + public IRetrieveFileTransferFactory getRetrieveFileTransferFactory() { + return getFileTransferServiceTracker().getService(); + } + + public synchronized void useJREHttpClient() { + IFileTransferProtocolToFactoryMapper mapper = getProtocolToFactoryMapper(); + if (mapper != null) { + // remove http + // Remove browse provider + String providerId = mapper.getBrowseFileTransferFactoryId(HTTP); + if (providerId != null) { + mapper.removeBrowseFileTransferFactory(providerId); + } + // Remove retrieve provider + providerId = mapper.getRetrieveFileTransferFactoryId(HTTP); + if (providerId != null) { + mapper.removeRetrieveFileTransferFactory(providerId); + } + // Remove send provider + providerId = mapper.getSendFileTransferFactoryId(HTTP); + if (providerId != null) { + mapper.removeSendFileTransferFactory(providerId); + } + // remove https + // Remove browse provider + providerId = mapper.getBrowseFileTransferFactoryId(HTTPS); + if (providerId != null) { + mapper.removeBrowseFileTransferFactory(providerId); + } + // Remove retrieve provider + providerId = mapper.getRetrieveFileTransferFactoryId(HTTPS); + if (providerId != null) { + mapper.removeRetrieveFileTransferFactory(providerId); + } + // Remove send provider + providerId = mapper.getSendFileTransferFactoryId(HTTPS); + if (providerId != null) { + mapper.removeSendFileTransferFactory(providerId); + } + } + } + + /** + * Gets the singleton ServiceTracker for the IRetrieveFileTransferFactory and starts the bundles + * "org.eclipse.ecf" and + * "org.eclipse.ecf.provider.filetransfer" on first call. + * @return ServiceTracker + */ + private synchronized ServiceTracker<IRetrieveFileTransferFactory, IRetrieveFileTransferFactory> getFileTransferServiceTracker() { + if (retrievalFactoryTracker == null) { + retrievalFactoryTracker = new ServiceTracker<IRetrieveFileTransferFactory, IRetrieveFileTransferFactory>(Activator.getContext(), IRetrieveFileTransferFactory.class, null); + retrievalFactoryTracker.open(); + startBundle("org.eclipse.ecf"); //$NON-NLS-1$ + startBundle("org.eclipse.ecf.provider.filetransfer"); //$NON-NLS-1$ + } + return retrievalFactoryTracker; + } + + private IFileTransferProtocolToFactoryMapper getProtocolToFactoryMapper() { + if (protocolToFactoryMapperTracker == null) { + protocolToFactoryMapperTracker = new ServiceTracker<IFileTransferProtocolToFactoryMapper, IFileTransferProtocolToFactoryMapper>(context, IFileTransferProtocolToFactoryMapper.class, null); + protocolToFactoryMapperTracker.open(); + } + return protocolToFactoryMapperTracker.getService(); + } + + private boolean startBundle(String bundleId) { + PackageAdmin packageAdmin = (PackageAdmin) ServiceHelper.getService(Activator.getContext(), PackageAdmin.class.getName()); + if (packageAdmin == null) + return false; + + Bundle[] bundles = packageAdmin.getBundles(bundleId, null); + if (bundles != null && bundles.length > 0) { + for (int i = 0; i < bundles.length; i++) { + try { + if ((bundles[i].getState() & Bundle.INSTALLED) == 0) { + bundles[i].start(Bundle.START_ACTIVATION_POLICY); + bundles[i].start(Bundle.START_TRANSIENT); + return true; + } + } catch (BundleException e) { + // failed, try next bundle + } + } + } + return false; + } + +} diff --git a/bundles/org.eclipse.equinox.p2.transport.ecf/src/org/eclipse/equinox/internal/p2/transport/ecf/ECFTransportComponent.java b/bundles/org.eclipse.equinox.p2.transport.ecf/src/org/eclipse/equinox/internal/p2/transport/ecf/ECFTransportComponent.java new file mode 100644 index 000000000..18a391407 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.transport.ecf/src/org/eclipse/equinox/internal/p2/transport/ecf/ECFTransportComponent.java @@ -0,0 +1,13 @@ +package org.eclipse.equinox.internal.p2.transport.ecf; + +import org.eclipse.equinox.p2.core.IProvisioningAgent; +import org.eclipse.equinox.p2.core.spi.IAgentServiceFactory; + +public class ECFTransportComponent implements IAgentServiceFactory { + + @Override + public Object createService(IProvisioningAgent agent) { + return new RepositoryTransport(); + } + +} diff --git a/bundles/org.eclipse.equinox.p2.transport.ecf/src/org/eclipse/equinox/internal/p2/transport/ecf/FileInfoReader.java b/bundles/org.eclipse.equinox.p2.transport.ecf/src/org/eclipse/equinox/internal/p2/transport/ecf/FileInfoReader.java new file mode 100644 index 000000000..2fc0afb28 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.transport.ecf/src/org/eclipse/equinox/internal/p2/transport/ecf/FileInfoReader.java @@ -0,0 +1,247 @@ +/******************************************************************************* + * Copyright (c) 2009, IBM Corporation, and others. + * The code, documentation and other materials contained herein have been + * licensed under the Eclipse Public License - v 1.0 by the copyright holder + * listed above, as the Initial Contributor under such license. The text of + * such license is available at www.eclipse.org. + * Contributors: + * IBM Corporation - initial implementation + * Cloudsmith Inc - modified API, and implementation + ******************************************************************************/ +package org.eclipse.equinox.internal.p2.transport.ecf; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.net.URI; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.ecf.core.*; +import org.eclipse.ecf.core.security.IConnectContext; +import org.eclipse.ecf.filetransfer.*; +import org.eclipse.ecf.filetransfer.events.IRemoteFileSystemBrowseEvent; +import org.eclipse.ecf.filetransfer.events.IRemoteFileSystemEvent; +import org.eclipse.ecf.filetransfer.identity.*; +import org.eclipse.equinox.internal.p2.core.helpers.LogHelper; +import org.eclipse.equinox.internal.p2.repository.Activator; +import org.eclipse.equinox.internal.p2.repository.AuthenticationFailedException; +import org.eclipse.equinox.internal.p2.repository.JREHttpClientRequiredException; +import org.eclipse.equinox.internal.p2.repository.Messages; +import org.eclipse.equinox.internal.p2.repository.RepositoryPreferences; +import org.eclipse.osgi.util.NLS; + +/** + * The FileInfoReader is a {@link Job} similar to {@link FileReader}, but without the support + * from ECF (there is currently no way to wait on a BrowseRequest job, as this is internal to + * ECF). If such support is added, this class is easily modified. + * + */ +public class FileInfoReader extends Job implements IRemoteFileSystemListener { + private Exception exception; + private IProgressMonitor theMonitor; + private final int connectionRetryCount; + private final long connectionRetryDelay; + private final IConnectContext connectContext; + final Boolean[] barrier = new Boolean[1]; + private IRemoteFile[] remoteFiles; + private IRemoteFileSystemRequest browseRequest; + + /* (non-Javadoc) + * @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor) + */ + protected IStatus run(IProgressMonitor monitor) { + synchronized (barrier) { + while (barrier[0] == null) { + try { + barrier.wait(1000); + if (theMonitor.isCanceled() && browseRequest != null) + browseRequest.cancel(); + } catch (InterruptedException e) { + //ignore + } + } + } + return Status.OK_STATUS; + } + + /** + * Waits until request is processed (barrier[0] is non null). + * This is a bit of a hack, as it would be better if the ECFBrowser worked in similar fashion to + * file transfer were a custom job can be supplied. + * TODO: log an issue for ECF. + */ + private void waitOnSelf() { + schedule(); + while (barrier[0] == null) { + boolean logged = false; + try { + join(); + } catch (InterruptedException e) { + if (!logged) + LogHelper.log(new Status(IStatus.WARNING, Activator.ID, "Unexpected interrupt while waiting on ECF browse request", e)); //$NON-NLS-1$ + } + } + } + + /** + * Create a new FileInfoReader that will retry failed connection attempts and sleep some amount of time between each + * attempt. + */ + public FileInfoReader(IConnectContext aConnectContext) { + super(Messages.repo_loading); // job label - TODO: this is a bad label + barrier[0] = null; + // Hide this job. + setSystem(true); + setUser(false); + connectionRetryCount = RepositoryPreferences.getConnectionRetryCount(); + connectionRetryDelay = RepositoryPreferences.getConnectionMsRetryDelay(); + connectContext = aConnectContext; + } + + /** + * Get the requested information. + * @return IRemoteFile[] or null if there was an error. + * @throws CoreException + * @throws FileNotFoundException + * @throws AuthenticationFailedException + * @throws JREHttpClientRequiredException + */ + public IRemoteFile[] getRemoteFiles(URI location, IProgressMonitor monitor) throws AuthenticationFailedException, FileNotFoundException, CoreException, JREHttpClientRequiredException { + if (monitor != null) + monitor.beginTask(location.toString(), 1); + try { + sendBrowseRequest(location, monitor); + waitOnSelf(); + // throw any exception received in a callback + checkException(location, connectionRetryCount); + + return remoteFiles; + } finally { + if (monitor != null) { + monitor.done(); + } + } + + } + + public IRemoteFile getRemoteFile(URI location, IProgressMonitor monitor) throws AuthenticationFailedException, FileNotFoundException, CoreException, JREHttpClientRequiredException { + + getRemoteFiles(location, monitor); + return remoteFiles != null && remoteFiles.length > 0 ? remoteFiles[0] : null; + } + + public long getLastModified(URI location, IProgressMonitor monitor) throws AuthenticationFailedException, FileNotFoundException, CoreException, JREHttpClientRequiredException { + IRemoteFile file = getRemoteFile(location, monitor); + if (file == null) + throw new FileNotFoundException(location.toString()); + return file.getInfo().getLastModified(); + } + + public void handleRemoteFileEvent(IRemoteFileSystemEvent event) { + exception = event.getException(); + if (exception != null) { + synchronized (barrier) { + barrier[0] = Boolean.TRUE; + barrier.notify(); + } + } else if (event instanceof IRemoteFileSystemBrowseEvent) { + IRemoteFileSystemBrowseEvent fsbe = (IRemoteFileSystemBrowseEvent) event; + remoteFiles = fsbe.getRemoteFiles(); + if (theMonitor != null) + theMonitor.worked(1); + synchronized (barrier) { + barrier[0] = Boolean.TRUE; + barrier.notify(); + } + } else { + synchronized (barrier) { + barrier[0] = Boolean.FALSE; // ended by unknown reason + barrier.notify(); + } + } + } + + protected void sendBrowseRequest(URI uri, IProgressMonitor monitor) throws CoreException, FileNotFoundException, AuthenticationFailedException, JREHttpClientRequiredException { + IContainer container; + try { + container = ContainerFactory.getDefault().createContainer(); + } catch (ContainerCreateException e) { + throw RepositoryStatusHelper.fromMessage(Messages.ecf_configuration_error); + } + + IRemoteFileSystemBrowserContainerAdapter adapter = (IRemoteFileSystemBrowserContainerAdapter) container.getAdapter(IRemoteFileSystemBrowserContainerAdapter.class); + if (adapter == null) { + throw RepositoryStatusHelper.fromMessage(Messages.ecf_configuration_error); + } + + adapter.setConnectContextForAuthentication(connectContext); + + this.exception = null; + this.theMonitor = monitor; + for (int retryCount = 0;; retryCount++) { + if (monitor != null && monitor.isCanceled()) + throw new OperationCanceledException(); + + try { + IFileID fileID = FileIDFactory.getDefault().createFileID(adapter.getBrowseNamespace(), uri.toString()); + browseRequest = adapter.sendBrowseRequest(fileID, this); + } catch (RemoteFileSystemException e) { + exception = e; + } catch (FileCreateException e) { + exception = e; + } + if (checkException(uri, retryCount)) + break; + } + } + + protected Exception getException() { + return exception; + } + + /** + * Utility method to check exception condition and determine if retry should be done. + * If there was an exception it is translated into one of the specified exceptions and thrown. + * + * @param uri the URI being read - used for logging purposes + * @param attemptCounter - the current attempt number (start with 0) + * @return true if the exception is an IOException and attemptCounter < connectionRetryCount, false otherwise + * @throws CoreException + * @throws FileNotFoundException + * @throws AuthenticationFailedException + * @throws JREHttpClientRequiredException + */ + private boolean checkException(URI uri, int attemptCounter) throws CoreException, FileNotFoundException, AuthenticationFailedException, JREHttpClientRequiredException { + // note that 'exception' could have been captured in a callback + if (exception != null) { + // check if HTTP client needs to be changed + RepositoryStatusHelper.checkJREHttpClientRequired(exception); + + // if this is a authentication failure - it is not meaningful to continue + RepositoryStatusHelper.checkPermissionDenied(exception); + + // if this is a file not found - it is not meaningful to continue + RepositoryStatusHelper.checkFileNotFound(exception, uri); + + Throwable t = RepositoryStatusHelper.unwind(exception); + if (t instanceof CoreException) + throw RepositoryStatusHelper.unwindCoreException((CoreException) t); + + if (t instanceof IOException && attemptCounter < connectionRetryCount) { + // TODO: Retry only certain exceptions or filter out + // some exceptions not worth retrying + // + exception = null; + try { + LogHelper.log(new Status(IStatus.WARNING, Activator.ID, NLS.bind(Messages.connection_to_0_failed_on_1_retry_attempt_2, new String[] {uri.toString(), t.getMessage(), String.valueOf(attemptCounter)}), t)); + + Thread.sleep(connectionRetryDelay); + return false; + } catch (InterruptedException e) { + /* ignore */ + } + } + throw RepositoryStatusHelper.wrap(exception); + } + return true; + } +} diff --git a/bundles/org.eclipse.equinox.p2.transport.ecf/src/org/eclipse/equinox/internal/p2/transport/ecf/FileReader.java b/bundles/org.eclipse.equinox.p2.transport.ecf/src/org/eclipse/equinox/internal/p2/transport/ecf/FileReader.java new file mode 100644 index 000000000..d1760c6a8 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.transport.ecf/src/org/eclipse/equinox/internal/p2/transport/ecf/FileReader.java @@ -0,0 +1,509 @@ +/******************************************************************************* + * Copyright (c) 2006, 2010 Cloudsmith 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: + * Cloudsmith Inc - initial API and implementation + * IBM Corporation - ongoing development + * Sonatype Inc - ongoing development + ******************************************************************************/ +package org.eclipse.equinox.internal.p2.transport.ecf; + +import java.io.*; +import java.net.SocketTimeoutException; +import java.net.URI; +import java.util.Date; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.ecf.core.security.IConnectContext; +import org.eclipse.ecf.filetransfer.*; +import org.eclipse.ecf.filetransfer.events.*; +import org.eclipse.ecf.filetransfer.identity.*; +import org.eclipse.ecf.filetransfer.service.IRetrieveFileTransferFactory; +import org.eclipse.equinox.internal.p2.core.helpers.LogHelper; +import org.eclipse.equinox.internal.p2.repository.AuthenticationFailedException; +import org.eclipse.equinox.internal.p2.repository.FileInfo; +import org.eclipse.equinox.internal.p2.repository.JREHttpClientRequiredException; +import org.eclipse.equinox.internal.p2.repository.Messages; +import org.eclipse.equinox.internal.p2.repository.ProgressStatistics; +import org.eclipse.equinox.internal.p2.repository.RepositoryPreferences; +import org.eclipse.equinox.internal.p2.repository.RepositoryTracing; +import org.eclipse.osgi.util.NLS; + +/** + * FileReader is an ECF FileTransferJob implementation. + */ +public final class FileReader extends FileTransferJob implements IFileTransferListener { + /** + * Class used to suppress warnings about a job being blocked by another job. + * Since we are running a job that will always be blocked by another job that + * is actually performing the transfer, these messages are unnecessary and ugly. + */ + static class SuppressBlockedMonitor extends SubProgressMonitor { + public SuppressBlockedMonitor(IProgressMonitor monitor, int ticks) { + super(monitor, ticks); + } + + public void setBlocked(IStatus reason) { + //do nothing + } + + public void clearBlocked() { + //do nothing + } + } + + private static IFileReaderProbe testProbe; + private boolean closeStreamWhenFinished = false; + private Exception exception; + private FileInfo fileInfo; + private long lastProgressCount; + private long lastStatsCount; + protected IProgressMonitor theMonitor; + private OutputStream theOutputStream; + private ProgressStatistics statistics; + private final int connectionRetryCount; + private final long connectionRetryDelay; + private final IConnectContext connectContext; + private URI requestUri; + protected IFileTransferConnectStartEvent connectEvent; + private Job cancelJob; + private boolean monitorStarted; + + /** + * Create a new FileReader that will retry failed connection attempts and sleep some amount of time between each + * attempt. + */ + public FileReader(IConnectContext aConnectContext) { + super(Messages.FileTransport_reader); // job label + + // Hide this job. + setSystem(true); + setUser(false); + connectionRetryCount = RepositoryPreferences.getConnectionRetryCount(); + connectionRetryDelay = RepositoryPreferences.getConnectionMsRetryDelay(); + connectContext = aConnectContext; + } + + public FileInfo getLastFileInfo() { + return fileInfo; + } + + /** + * A job to handle cancelation when trying to establish a socket connection. + * At this point we don't have a transfer job running yet, so we need a separate + * job to monitor for cancelation. + */ + protected class CancelHandler extends Job { + private boolean done = false; + + protected CancelHandler() { + super(Messages.FileTransport_cancelCheck); + setSystem(true); + } + + public IStatus run(IProgressMonitor jobMonitor) { + while (!done && !jobMonitor.isCanceled()) { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + return Status.CANCEL_STATUS; + } + if (theMonitor != null && theMonitor.isCanceled()) + if (connectEvent != null) + connectEvent.cancel(); + } + return Status.OK_STATUS; + } + + protected void canceling() { + //wake up from sleep in run method + Thread t = getThread(); + if (t != null) + t.interrupt(); + } + + } + + public synchronized void handleTransferEvent(IFileTransferEvent event) { + if (event instanceof IFileTransferConnectStartEvent) { + // keep the connect event to be able to cancel the transfer + connectEvent = (IFileTransferConnectStartEvent) event; + cancelJob = new CancelHandler(); + //schedule with a delay to avoid the overhead of an extra job on a fast connection + cancelJob.schedule(500); + } else if (event instanceof IIncomingFileTransferReceiveStartEvent) { + //we no longer need the cancel handler because we are about to fork the transfer job + if (cancelJob != null) + cancelJob.cancel(); + IIncomingFileTransfer source = ((IIncomingFileTransferEvent) event).getSource(); + try { + FileInfo fi = new FileInfo(); + Date lastModified = source.getRemoteLastModified(); + if (lastModified != null) + fi.setLastModified(lastModified.getTime()); + fi.setName(source.getRemoteFileName()); + fi.setSize(source.getFileLength()); + fileInfo = fi; + + ((IIncomingFileTransferReceiveStartEvent) event).receive(theOutputStream, this); + } catch (IOException e) { + exception = e; + return; + } + long fileLength = source.getFileLength(); + ProgressStatistics stats = new ProgressStatistics(requestUri, source.getRemoteFileName(), fileLength); + setStatistics(stats); + + if (theMonitor != null) { + theMonitor.beginTask(null, 1000); + monitorStarted = true; + theMonitor.subTask(stats.report()); + lastStatsCount = 0; + lastProgressCount = 0; + } + onStart(source); + } else if (event instanceof IIncomingFileTransferReceiveDataEvent) { + IIncomingFileTransfer source = ((IIncomingFileTransferEvent) event).getSource(); + if (theMonitor != null) { + if (theMonitor.isCanceled()) { + source.cancel(); + return; + } + + long br = source.getBytesReceived(); + long count = br - lastStatsCount; + lastStatsCount = br; + ProgressStatistics stats = getStatistics(); + if (stats != null) { + stats.increase(count); + fileInfo.setAverageSpeed(stats.getAverageSpeed()); + if (stats.shouldReport()) { + count = br - lastProgressCount; + lastProgressCount = br; + theMonitor.subTask(stats.report()); + theMonitor.worked((int) (1000 * count / stats.getTotal())); + } + } + } + onData(source); + } else if (event instanceof IIncomingFileTransferReceiveDoneEvent) { + if (closeStreamWhenFinished) + hardClose(theOutputStream); + + if (exception == null) + exception = ((IIncomingFileTransferReceiveDoneEvent) event).getException(); + onDone(((IIncomingFileTransferReceiveDoneEvent) event).getSource()); + } + } + + public InputStream read(URI url, final IProgressMonitor monitor) throws CoreException, FileNotFoundException, AuthenticationFailedException, JREHttpClientRequiredException { + final PipedInputStream input = new PipedInputStream(); + PipedOutputStream output; + try { + output = new PipedOutputStream(input); + } catch (IOException e) { + throw RepositoryStatusHelper.wrap(e); + } + RepositoryTracing.debug("Downloading {0}", url); //$NON-NLS-1$ + + sendRetrieveRequest(url, output, null, true, monitor); + + return new InputStream() { + public int available() throws IOException { + checkException(); + return input.available(); + } + + public void close() throws IOException { + hardClose(input); + checkException(); + } + + public void mark(int readlimit) { + input.mark(readlimit); + } + + public boolean markSupported() { + return input.markSupported(); + } + + public int read() throws IOException { + checkException(); + return input.read(); + } + + public int read(byte b[]) throws IOException { + checkException(); + return input.read(b); + } + + public int read(byte b[], int off, int len) throws IOException { + checkException(); + return input.read(b, off, len); + } + + public void reset() throws IOException { + checkException(); + input.reset(); + } + + public long skip(long n) throws IOException { + checkException(); + return input.skip(n); + } + + private void checkException() throws IOException { + if (getException() == null) + return; + + IOException e; + Throwable t = RepositoryStatusHelper.unwind(getException()); + if (t instanceof IOException) + e = (IOException) t; + else { + if (t instanceof UserCancelledException) { + Throwable cause = t; + t = new OperationCanceledException(t.getMessage()); + t.initCause(cause); + } + e = new IOException(t.getMessage()); + e.initCause(t); + } + throw e; + } + }; + } + + public void readInto(URI uri, OutputStream anOutputStream, IProgressMonitor monitor) // + throws CoreException, FileNotFoundException, AuthenticationFailedException, JREHttpClientRequiredException { + readInto(uri, anOutputStream, -1, monitor); + } + + public boolean belongsTo(Object family) { + return family == this; + } + + public void readInto(URI uri, OutputStream anOutputStream, long startPos, IProgressMonitor monitor) // + throws CoreException, FileNotFoundException, AuthenticationFailedException, JREHttpClientRequiredException { + if (monitor == null) + monitor = new NullProgressMonitor(); + try { + sendRetrieveRequest(uri, anOutputStream, (startPos != -1 ? new DownloadRange(startPos) : null), false, monitor); + Job.getJobManager().join(this, new SuppressBlockedMonitor(monitor, 0)); + if (monitor.isCanceled() && connectEvent != null) + connectEvent.cancel(); + // check and throw exception if received in callback + checkException(uri, connectionRetryCount); + } catch (InterruptedException e) { + monitor.setCanceled(true); + throw new OperationCanceledException(); + } finally { + // kill the cancelJob, if there is one + if (cancelJob != null) { + cancelJob.cancel(); + cancelJob = null; + } + // If monitor was never started, make sure it is balanced + if (!monitorStarted) + monitor.beginTask(null, 1); + monitorStarted = false; + monitor.done(); + } + } + + protected void sendRetrieveRequest(URI uri, OutputStream outputStream, DownloadRange range, boolean closeStreamOnFinish, // + IProgressMonitor monitor) throws CoreException, FileNotFoundException, AuthenticationFailedException, JREHttpClientRequiredException { + + IRetrieveFileTransferFactory factory = Activator.getDefault().getRetrieveFileTransferFactory(); + if (factory == null) { + throw RepositoryStatusHelper.fromMessage(Messages.ecf_configuration_error); + } + IRetrieveFileTransferContainerAdapter adapter = factory.newInstance(); + + adapter.setConnectContextForAuthentication(connectContext); + + this.exception = null; + this.closeStreamWhenFinished = closeStreamOnFinish; + this.fileInfo = null; + this.statistics = null; + this.lastProgressCount = 0L; + this.lastStatsCount = 0L; + this.theMonitor = monitor; + this.monitorStarted = false; + this.theOutputStream = outputStream; + this.requestUri = uri; + + for (int retryCount = 0;; retryCount++) { + if (monitor != null && monitor.isCanceled()) + throw new OperationCanceledException(); + + try { + IFileID fileID = FileIDFactory.getDefault().createFileID(adapter.getRetrieveNamespace(), uri.toString()); + if (range != null) + adapter.sendRetrieveRequest(fileID, range, this, null); + else + adapter.sendRetrieveRequest(fileID, this, null); + } catch (IncomingFileTransferException e) { + exception = e; + } catch (FileCreateException e) { + exception = e; + } catch (Throwable t) { + if (exception != null) + exception.printStackTrace(); + } + if (checkException(uri, retryCount)) + break; + } + } + + /** + * Utility method to check exception condition and determine if retry should be done. + * If there was an exception it is translated into one of the specified exceptions and thrown. + * + * @param uri the URI being read - used for logging purposes + * @param attemptCounter - the current attempt number (start with 0) + * @return true if the exception is an IOException and attemptCounter < connectionRetryCount, false otherwise + * @throws CoreException + * @throws FileNotFoundException + * @throws AuthenticationFailedException + */ + private boolean checkException(URI uri, int attemptCounter) throws CoreException, FileNotFoundException, AuthenticationFailedException, JREHttpClientRequiredException { + // note that 'exception' could have been captured in a callback + if (exception != null) { + // check if HTTP client needs to be changed + RepositoryStatusHelper.checkJREHttpClientRequired(exception); + + // if this is an 'authentication failure' - it is not meaningful to continue + RepositoryStatusHelper.checkPermissionDenied(exception); + + // if this is a 'file not found' - it is not meaningful to continue + RepositoryStatusHelper.checkFileNotFound(exception, uri); + + Throwable t = RepositoryStatusHelper.unwind(exception); + if (t instanceof CoreException) + throw RepositoryStatusHelper.unwindCoreException((CoreException) t); + + // not meaningful to try 'timeout again' - if a server is that busy, we + // need to wait for quite some time before retrying- it is not likely it is + // just a temporary network thing. + if (t instanceof SocketTimeoutException) + throw RepositoryStatusHelper.wrap(t); + + if (t instanceof IOException && attemptCounter < connectionRetryCount) { + // TODO: Retry only certain exceptions or filter out + // some exceptions not worth retrying + // + exception = null; + try { + LogHelper.log(new Status(IStatus.WARNING, Activator.ID, NLS.bind(Messages.connection_to_0_failed_on_1_retry_attempt_2, new String[] {uri.toString(), t.getMessage(), String.valueOf(attemptCounter)}), t)); + + Thread.sleep(connectionRetryDelay); + return false; + } catch (InterruptedException e) { + /* ignore */ + } + } + throw RepositoryStatusHelper.wrap(exception); + } + return true; + } + + protected Exception getException() { + return exception; + } + + /** + * Closes input and output streams + * @param aStream + */ + public static void hardClose(Object aStream) { + if (aStream != null) { + try { + if (aStream instanceof OutputStream) + ((OutputStream) aStream).close(); + else if (aStream instanceof InputStream) + ((InputStream) aStream).close(); + } catch (IOException e) { /* ignore */ + } + } + } + + private static class DownloadRange implements IFileRangeSpecification { + + private long startPosition; + + public DownloadRange(long startPos) { + startPosition = startPos; + } + + public long getEndPosition() { + return -1; + } + + public long getStartPosition() { + return startPosition; + } + + } + + private void onDone(IIncomingFileTransfer source) { + if (testProbe != null) + testProbe.onDone(this, source, theMonitor); + } + + private void onStart(IIncomingFileTransfer source) { + if (testProbe != null) + testProbe.onStart(this, source, theMonitor); + } + + private void onData(IIncomingFileTransfer source) { + if (testProbe != null) + testProbe.onData(this, source, theMonitor); + } + + /** + * Sets a testing probe that can intercept events on the file reader for testing purposes. + * This method should only ever be called from automated test suites. + */ + public static void setTestProbe(IFileReaderProbe probe) { + testProbe = probe; + } + + /** + * Sets the progress statistics. This method is synchronized because the field + * is accessed from both the transfer thread and the thread initiating the transfer + * and we need to ensure field values are consistent across threads. + * + * @param statistics the statistics to set, or <code>null</code> + */ + private synchronized void setStatistics(ProgressStatistics statistics) { + this.statistics = statistics; + } + + /** + * Returns the progress statistics. This method is synchronized because the field + * is accessed from both the transfer thread and the thread initiating the transfer + * and we need to ensure field values are consistent across threads. + * + * @return the statistics, or <code>null</code> + */ + private synchronized ProgressStatistics getStatistics() { + return statistics; + } + + /** + * An interface to allow automated tests to hook into file reader events + * @see #setTestProbe + */ + public interface IFileReaderProbe { + public void onStart(FileReader reader, IIncomingFileTransfer source, IProgressMonitor monitor); + + public void onData(FileReader reader, IIncomingFileTransfer source, IProgressMonitor monitor); + + public void onDone(FileReader reader, IIncomingFileTransfer source, IProgressMonitor monitor); + } +} diff --git a/bundles/org.eclipse.equinox.p2.transport.ecf/src/org/eclipse/equinox/internal/p2/transport/ecf/Messages.java b/bundles/org.eclipse.equinox.p2.transport.ecf/src/org/eclipse/equinox/internal/p2/transport/ecf/Messages.java new file mode 100644 index 000000000..8d51aaac3 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.transport.ecf/src/org/eclipse/equinox/internal/p2/transport/ecf/Messages.java @@ -0,0 +1,95 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 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 messages + * Sonatype Inc - ongoing development + *******************************************************************************/ +package org.eclipse.equinox.internal.p2.transport.ecf; + +import org.eclipse.osgi.util.NLS; + +public class Messages extends NLS { + private static final String BUNDLE_NAME = "org.eclipse.equinox.internal.p2.repository.messages"; //$NON-NLS-1$ + + public static String CacheManager_AuthenticationFaileFor_0; + public static String CacheManager_FailedCommunicationWithRepo_0; + public static String CacheManager_Neither_0_nor_1_found; + public static String CacheManage_ErrorRenamingCache; + + public static String artifact_not_found; + public static String io_failedRead; + public static String ecf_configuration_error; + public static String repoMan_internalError; + public static String repo_loading; + + public static String exception_malformedRepoURI; + public static String TransportErrorTranslator_400; + public static String TransportErrorTranslator_401; + public static String TransportErrorTranslator_402; + public static String TransportErrorTranslator_403; + public static String TransportErrorTranslator_404; + public static String TransportErrorTranslator_405; + public static String TransportErrorTranslator_406; + public static String TransportErrorTranslator_407; + public static String TransportErrorTranslator_408; + public static String TransportErrorTranslator_409; + public static String TransportErrorTranslator_410; + public static String TransportErrorTranslator_411; + public static String TransportErrorTranslator_412; + public static String TransportErrorTranslator_413; + public static String TransportErrorTranslator_414; + public static String TransportErrorTranslator_415; + public static String TransportErrorTranslator_416; + public static String TransportErrorTranslator_417; + public static String TransportErrorTranslator_418; + public static String TransportErrorTranslator_422; + public static String TransportErrorTranslator_423; + public static String TransportErrorTranslator_424; + public static String TransportErrorTranslator_425; + public static String TransportErrorTranslator_426; + public static String TransportErrorTranslator_449; + public static String TransportErrorTranslator_450; + public static String TransportErrorTranslator_500; + public static String TransportErrorTranslator_501; + public static String TransportErrorTranslator_502; + public static String TransportErrorTranslator_503; + public static String TransportErrorTranslator_504; + public static String TransportErrorTranslator_505; + public static String TransportErrorTranslator_506; + public static String TransportErrorTranslator_507; + public static String TransportErrorTranslator_508; + public static String TransportErrorTranslator_510; + public static String TransportErrorTranslator_MalformedRemoteFileReference; + public static String TransportErrorTranslator_UnableToConnectToRepository_0; + + public static String TransportErrorTranslator_UnknownErrorCode; + public static String TransportErrorTranslator_UnknownHost; + + public static String fetching_0_from_1_2_at_3; + public static String fetching_0_from_1_2_of_3_at_4; + public static String connection_to_0_failed_on_1_retry_attempt_2; + + public static String FileTransport_reader; + public static String FileTransport_cancelCheck; + + public static String UnableToRead_0_TooManyAttempts; + public static String UnableToRead_0_UserCanceled; + + public static String RepositoryTransport_failedReadRepo; + + static { + // initialize resource bundles + NLS.initializeMessages(BUNDLE_NAME, Messages.class); + } + + private Messages() { + // Do not instantiate + } + +}
\ No newline at end of file diff --git a/bundles/org.eclipse.equinox.p2.transport.ecf/src/org/eclipse/equinox/internal/p2/transport/ecf/RepositoryStatus.java b/bundles/org.eclipse.equinox.p2.transport.ecf/src/org/eclipse/equinox/internal/p2/transport/ecf/RepositoryStatus.java new file mode 100644 index 000000000..ccc465939 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.transport.ecf/src/org/eclipse/equinox/internal/p2/transport/ecf/RepositoryStatus.java @@ -0,0 +1,155 @@ +/******************************************************************************* + * Copyright (c) 2009 Cloudsmith Inc. 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: + * Cloudsmith Inc. - initial API and implementation + *******************************************************************************/ + +package org.eclipse.equinox.internal.p2.transport.ecf; + +import org.eclipse.equinox.internal.p2.repository.DownloadStatus; +import org.eclipse.equinox.p2.core.ProvisionException; + +import java.io.FileNotFoundException; +import java.net.*; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.ecf.core.identity.IDCreateException; +import org.eclipse.ecf.filetransfer.BrowseFileTransferException; +import org.eclipse.ecf.filetransfer.IncomingFileTransferException; +import org.eclipse.osgi.util.NLS; + +/** + * Utility class to transform transport errors into error messages. + * + */ +public class RepositoryStatus { + + public static String codeToMessage(int code, String toDownload) { + switch (code) { + case 400 : + return NLS.bind(Messages.TransportErrorTranslator_400, toDownload); + case 401 : + return NLS.bind(Messages.TransportErrorTranslator_401, toDownload); + case 402 : + return NLS.bind(Messages.TransportErrorTranslator_402, toDownload); + case 403 : + return NLS.bind(Messages.TransportErrorTranslator_403, toDownload); + case 404 : + return NLS.bind(Messages.TransportErrorTranslator_404, toDownload); + case 405 : + return NLS.bind(Messages.TransportErrorTranslator_405, toDownload); + case 406 : + return NLS.bind(Messages.TransportErrorTranslator_406, toDownload); + case 407 : + return NLS.bind(Messages.TransportErrorTranslator_407, toDownload); + case 408 : + return NLS.bind(Messages.TransportErrorTranslator_408, toDownload); + case 409 : + return NLS.bind(Messages.TransportErrorTranslator_409, toDownload); + case 410 : + return NLS.bind(Messages.TransportErrorTranslator_410, toDownload); + case 411 : + return NLS.bind(Messages.TransportErrorTranslator_411, toDownload); + case 412 : + return NLS.bind(Messages.TransportErrorTranslator_412, toDownload); + case 413 : + return NLS.bind(Messages.TransportErrorTranslator_413, toDownload); + case 414 : + return NLS.bind(Messages.TransportErrorTranslator_414, toDownload); + case 415 : + return NLS.bind(Messages.TransportErrorTranslator_415, toDownload); + case 416 : + return NLS.bind(Messages.TransportErrorTranslator_416, toDownload); + case 417 : + return NLS.bind(Messages.TransportErrorTranslator_417, toDownload); + case 418 : + return NLS.bind(Messages.TransportErrorTranslator_418, toDownload); + case 422 : + return NLS.bind(Messages.TransportErrorTranslator_422, toDownload); + case 423 : + return NLS.bind(Messages.TransportErrorTranslator_423, toDownload); + case 424 : + return NLS.bind(Messages.TransportErrorTranslator_424, toDownload); + case 425 : + return NLS.bind(Messages.TransportErrorTranslator_425, toDownload); + case 426 : + return NLS.bind(Messages.TransportErrorTranslator_426, toDownload); + case 449 : + return NLS.bind(Messages.TransportErrorTranslator_449, toDownload); + case 450 : + return NLS.bind(Messages.TransportErrorTranslator_450, toDownload); + + case 500 : + return NLS.bind(Messages.TransportErrorTranslator_500, toDownload); + case 501 : + return NLS.bind(Messages.TransportErrorTranslator_501, toDownload); + case 502 : + return NLS.bind(Messages.TransportErrorTranslator_502, toDownload); + case 503 : + return NLS.bind(Messages.TransportErrorTranslator_503, toDownload); + case 504 : + return NLS.bind(Messages.TransportErrorTranslator_504, toDownload); + case 505 : + return NLS.bind(Messages.TransportErrorTranslator_505, toDownload); + case 506 : + return NLS.bind(Messages.TransportErrorTranslator_506, toDownload); + case 507 : + return NLS.bind(Messages.TransportErrorTranslator_507, toDownload); + case 508 : + return NLS.bind(Messages.TransportErrorTranslator_508, toDownload); + case 510 : + return NLS.bind(Messages.TransportErrorTranslator_510, toDownload); + + default : + return NLS.bind(Messages.TransportErrorTranslator_UnknownErrorCode, Integer.toString(code), toDownload); + } + } + + public static DownloadStatus forStatus(IStatus original, URI toDownload) { + Throwable t = original.getException(); + return forException(t, toDownload); + } + + public static DownloadStatus forException(Throwable t, URI toDownload) { + if (t instanceof FileNotFoundException || (t instanceof IncomingFileTransferException && ((IncomingFileTransferException) t).getErrorCode() == 404)) + return new DownloadStatus(IStatus.ERROR, Activator.ID, ProvisionException.ARTIFACT_NOT_FOUND, NLS.bind(Messages.artifact_not_found, toDownload), t); + if (t instanceof ConnectException) + return new DownloadStatus(IStatus.ERROR, Activator.ID, ProvisionException.REPOSITORY_FAILED_READ, NLS.bind(Messages.TransportErrorTranslator_UnableToConnectToRepository_0, toDownload), t); + if (t instanceof UnknownHostException) + return new DownloadStatus(IStatus.ERROR, Activator.ID, ProvisionException.REPOSITORY_INVALID_LOCATION, NLS.bind(Messages.TransportErrorTranslator_UnknownHost, toDownload), t); + if (t instanceof IDCreateException) { + IStatus status = ((IDCreateException) t).getStatus(); + if (status != null && status.getException() != null) + t = status.getException(); + + return new DownloadStatus(IStatus.ERROR, Activator.ID, ProvisionException.REPOSITORY_INVALID_LOCATION, NLS.bind(Messages.TransportErrorTranslator_MalformedRemoteFileReference, toDownload), t); + } + int code = 0; + + // default to report as read repository error + int provisionCode = ProvisionException.REPOSITORY_FAILED_READ; + + if (t instanceof IncomingFileTransferException) + code = ((IncomingFileTransferException) t).getErrorCode(); + else if (t instanceof BrowseFileTransferException) + code = ((BrowseFileTransferException) t).getErrorCode(); + + // Switch on error codes in the HTTP error code range. + // Note that 404 uses ARTIFACT_NOT_FOUND (as opposed to REPOSITORY_NOT_FOUND, which + // is determined higher up in the calling chain). + if (code == 401) + provisionCode = ProvisionException.REPOSITORY_FAILED_AUTHENTICATION; + else if (code == 404) + provisionCode = ProvisionException.ARTIFACT_NOT_FOUND; + + // Add more specific translation here + + return new DownloadStatus(IStatus.ERROR, Activator.ID, provisionCode, // + code == 0 ? NLS.bind(Messages.io_failedRead, toDownload) // + : codeToMessage(code, toDownload.toString()), t); + } +} diff --git a/bundles/org.eclipse.equinox.p2.transport.ecf/src/org/eclipse/equinox/internal/p2/transport/ecf/RepositoryStatusHelper.java b/bundles/org.eclipse.equinox.p2.transport.ecf/src/org/eclipse/equinox/internal/p2/transport/ecf/RepositoryStatusHelper.java new file mode 100644 index 000000000..f2fb848f7 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.transport.ecf/src/org/eclipse/equinox/internal/p2/transport/ecf/RepositoryStatusHelper.java @@ -0,0 +1,320 @@ +/******************************************************************************* + * Copyright (c) 2009 Cloudsmith Inc, and other. + * The code, documentation and other materials contained herein have been + * licensed under the Eclipse Public License - v 1.0 by the individual + * copyright holders listed above, as Initial Contributors under such license. + * The text of such license is available at www.eclipse.org. + * Contributors: + * Cloudsmith Inc. - Initial API and implementation + * IBM Corporation - Original Implementation of checkPermissionDenied + *******************************************************************************/ +package org.eclipse.equinox.internal.p2.transport.ecf; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.PrintStream; +import java.lang.reflect.InvocationTargetException; +import java.net.URI; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.core.runtime.Status; +import org.eclipse.ecf.filetransfer.BrowseFileTransferException; +import org.eclipse.ecf.filetransfer.IncomingFileTransferException; +import org.eclipse.equinox.internal.p2.repository.AuthenticationFailedException; +import org.eclipse.equinox.internal.p2.repository.JREHttpClientRequiredException; +import org.eclipse.equinox.p2.core.ProvisionException; +import org.eclipse.osgi.util.NLS; + +/** + * RepositoryStatusHelper is a utility class for processing of exceptions and status. + */ +public abstract class RepositoryStatusHelper { + + private static final long serialVersionUID = 1L; + protected static final String SERVER_REDIRECT = "Server redirected too many times"; //$NON-NLS-1$ + + public static IStatus createStatus(String nlsMessage, Object arg) { + return createExceptionStatus(null, nlsMessage, new Object[] {arg}); + } + + public static IStatus createStatus(String nlsMessage, Object arg1, Object arg2) { + return createExceptionStatus(null, nlsMessage, new Object[] {arg1, arg2}); + } + + public static IStatus createStatus(String nlsMessage, Object arg1, Object arg2, Object arg3) { + return createExceptionStatus(null, nlsMessage, new Object[] {arg1, arg2, arg3}); + } + + public static IStatus createStatus(String nlsMessage, Object[] args) { + return createExceptionStatus(null, nlsMessage, args); + } + + public static IStatus createStatus(String nlsMessage) { + return createExceptionStatus(null, nlsMessage, new Object[] {}); + } + + public static IStatus createExceptionStatus(Throwable cause) { + return (cause instanceof CoreException) ? ((CoreException) cause).getStatus() : new Status(IStatus.ERROR, Activator.ID, IStatus.OK, cause.getMessage(), cause); + } + + public static IStatus createExceptionStatus(Throwable cause, String nlsMessage, Object[] args) { + if (args != null && args.length > 0) + nlsMessage = NLS.bind(nlsMessage, args); + return new Status(IStatus.ERROR, Activator.ID, IStatus.OK, nlsMessage, cause); + } + + public static IStatus createExceptionStatus(Throwable cause, String nlsMessage, Object arg1, Object arg2, Object arg3) { + return createExceptionStatus(cause, nlsMessage, new Object[] {arg1, arg2, arg3}); + } + + public static IStatus createExceptionStatus(Throwable cause, String nlsMessage, Object arg1, Object arg2) { + return createExceptionStatus(cause, nlsMessage, new Object[] {arg1, arg2}); + } + + public static IStatus createExceptionStatus(Throwable cause, String nlsMessage, Object arg1) { + return createExceptionStatus(cause, nlsMessage, new Object[] {arg1}); + } + + public static IStatus createExceptionStatus(Throwable cause, String nlsMessage) { + return createExceptionStatus(cause, nlsMessage, new Object[] {}); + } + + public static void deeplyPrint(Throwable e, PrintStream strm, boolean stackTrace) { + deeplyPrint(e, strm, stackTrace, 0); + } + + public static CoreException fromMessage(String nlsMessage, Object[] args) { + return fromExceptionMessage(null, nlsMessage, args); + } + + public static CoreException fromMessage(String nlsMessage, Object arg1) { + return fromExceptionMessage(null, nlsMessage, new Object[] {arg1}); + } + + public static CoreException fromMessage(String nlsMessage, Object arg1, Object arg2) { + return fromExceptionMessage(null, nlsMessage, new Object[] {arg1, arg2}); + } + + public static CoreException fromMessage(String nlsMessage, Object arg1, Object arg2, Object arg3) { + return fromExceptionMessage(null, nlsMessage, new Object[] {arg1, arg2, arg3}); + } + + public static CoreException fromMessage(String nlsMessage) { + return fromExceptionMessage(null, nlsMessage, new Object[] {}); + } + + public static CoreException fromExceptionMessage(Throwable cause, String nlsMessage, Object[] args) { + CoreException ce = new CoreException(createExceptionStatus(cause, nlsMessage, args)); + if (cause != null) + ce.initCause(cause); + return ce; + } + + public static CoreException fromExceptionMessage(Throwable cause, String nlsMessage, Object arg1, Object arg2, Object arg3) { + return fromExceptionMessage(cause, nlsMessage, new Object[] {arg1, arg2, arg3}); + } + + public static CoreException fromExceptionMessage(Throwable cause, String nlsMessage, Object arg1, Object arg2) { + return fromExceptionMessage(cause, nlsMessage, new Object[] {arg1, arg2}); + } + + public static CoreException fromExceptionMessage(Throwable cause, String nlsMessage, Object arg1) { + return fromExceptionMessage(cause, nlsMessage, new Object[] {arg1}); + } + + public static CoreException fromExceptionMessage(Throwable cause, String nlsMessage) { + return fromExceptionMessage(cause, nlsMessage, new Object[] {}); + } + + public static Throwable unwind(Throwable t) { + for (;;) { + Class<? extends Throwable> tc = t.getClass(); + + // We don't use instanceof operator since we want + // the explicit class, not subclasses. + // + if (tc != RuntimeException.class && tc != InvocationTargetException.class && tc != IOException.class) + break; + + Throwable cause = t.getCause(); + if (cause == null) + break; + + String msg = t.getMessage(); + if (msg != null && !msg.equals(cause.toString())) + break; + + t = cause; + } + return t; + } + + public static CoreException unwindCoreException(CoreException exception) { + IStatus status = exception.getStatus(); + while (status != null && status.getException() instanceof CoreException) { + exception = (CoreException) status.getException(); + status = exception.getStatus(); + } + return exception; + } + + public static CoreException wrap(IStatus status) { + CoreException e = new CoreException(status); + Throwable t = status.getException(); + if (t != null) + e.initCause(t); + return e; + } + + public static CoreException wrap(Throwable t) { + t = unwind(t); + if (t instanceof CoreException) + return unwindCoreException((CoreException) t); + + if (t instanceof OperationCanceledException || t instanceof InterruptedException) + return new CoreException(Status.CANCEL_STATUS); + + String msg = t.toString(); + return fromExceptionMessage(t, msg); + } + + private static void appendLevelString(PrintStream strm, int level) { + if (level > 0) { + strm.print("[0"); //$NON-NLS-1$ + for (int idx = 1; idx < level; ++idx) { + strm.print('.'); + strm.print(level); + } + strm.print(']'); + } + } + + private static void deeplyPrint(CoreException ce, PrintStream strm, boolean stackTrace, int level) { + appendLevelString(strm, level); + if (stackTrace) + ce.printStackTrace(strm); + deeplyPrint(ce.getStatus(), strm, stackTrace, level); + } + + private static void deeplyPrint(IStatus status, PrintStream strm, boolean stackTrace, int level) { + appendLevelString(strm, level); + String msg = status.getMessage(); + strm.println(msg); + Throwable cause = status.getException(); + if (cause != null) { + strm.print("Caused by: "); //$NON-NLS-1$ + if (stackTrace || !(msg.equals(cause.getMessage()) || msg.equals(cause.toString()))) + deeplyPrint(cause, strm, stackTrace, level); + } + + if (status.isMultiStatus()) { + IStatus[] children = status.getChildren(); + for (int i = 0; i < children.length; i++) + deeplyPrint(children[i], strm, stackTrace, level + 1); + } + } + + private static void deeplyPrint(Throwable t, PrintStream strm, boolean stackTrace, int level) { + if (t instanceof CoreException) + deeplyPrint((CoreException) t, strm, stackTrace, level); + else { + appendLevelString(strm, level); + if (stackTrace) + t.printStackTrace(strm); + else { + strm.println(t.toString()); + Throwable cause = t.getCause(); + if (cause != null) { + strm.print("Caused by: "); //$NON-NLS-1$ + deeplyPrint(cause, strm, stackTrace, level); + } + } + } + } + + /** + * Check if the given exception represents that a switch to the JRE HTTP Client + * is required. ECF sets the HTTP status code 477 to indicate this. + * If the JRE HTTP client is required a JREHttpClientRequiredException is thrown. + */ + public static void checkJREHttpClientRequired(Throwable t) throws JREHttpClientRequiredException { + if (t instanceof IncomingFileTransferException) { + if (((IncomingFileTransferException) t).getErrorCode() == 477) + throw new JREHttpClientRequiredException(); + } else if (t instanceof BrowseFileTransferException) { + if (((BrowseFileTransferException) t).getErrorCode() == 477) + throw new JREHttpClientRequiredException(); + } + + } + + /** + * Check if the given exception represents a permission failure (401 for HTTP), + * and throw a AuthenticationFailedException if a permission failure was encountered. + */ + public static void checkPermissionDenied(Throwable t) throws AuthenticationFailedException { + // From Use of File Transfer + if (t instanceof IncomingFileTransferException) { + if (((IncomingFileTransferException) t).getErrorCode() == 401) + throw new AuthenticationFailedException(); + IStatus status = ((IncomingFileTransferException) t).getStatus(); + t = status == null ? t : status.getException(); + // From Use of Browse + } else if (t instanceof BrowseFileTransferException) { + if (((BrowseFileTransferException) t).getErrorCode() == 401) + throw new AuthenticationFailedException(); + IStatus status = ((BrowseFileTransferException) t).getStatus(); + t = status == null ? t : status.getException(); + } + + if (t == null || !(t instanceof IOException)) + return; + + // TODO: is this needed (for 401) now that ECF throws exceptions with codes? + // try to figure out if we have a 401 by parsing the exception message + // There is unfortunately no specific (general) exception for "redirected too many times" - which is commonly + // caused by a failed login. The message and exception are different in different implementations + // of http client. + String m = t.getMessage(); + if (m != null && (m.indexOf(" 401 ") != -1 || m.indexOf(SERVER_REDIRECT) != -1)) //$NON-NLS-1$ + throw new AuthenticationFailedException(); + if ("org.apache.commons.httpclient.RedirectException".equals(t.getClass().getName())) //$NON-NLS-1$ + throw new AuthenticationFailedException(); + } + + /** + * Translates exceptions representing "FileNotFound" into FileNotFoundException. + * @param t the throwable to check + * @param toDownload the URI the exception was thrown for + * @throws FileNotFoundException if 't' represents a file not found + */ + public static void checkFileNotFound(Throwable t, URI toDownload) throws FileNotFoundException { + if (t instanceof IncomingFileTransferException) { + IncomingFileTransferException e = (IncomingFileTransferException) t; + if (e.getErrorCode() == 404 || e.getErrorCode() == 403 || e.getErrorCode() == 300) + throw new FileNotFoundException(toDownload.toString()); + } + if (t instanceof BrowseFileTransferException) { + BrowseFileTransferException e = (BrowseFileTransferException) t; + if (e.getErrorCode() == 404 || e.getErrorCode() == 403 || e.getErrorCode() == 300) + throw new FileNotFoundException(toDownload.toString()); + } + + if (t instanceof FileNotFoundException) + throw (FileNotFoundException) t; + if (t instanceof CoreException) { + IStatus status = ((CoreException) t).getStatus(); + Throwable e = status == null ? null : status.getException(); + if (e instanceof FileNotFoundException) + throw (FileNotFoundException) e; + } + } + + public static IStatus malformedAddressStatus(String address, Throwable t) { + return new Status(IStatus.ERROR, Activator.ID, // + ProvisionException.REPOSITORY_INVALID_LOCATION, NLS.bind(Messages.exception_malformedRepoURI, address), t); + + } +} diff --git a/bundles/org.eclipse.equinox.p2.transport.ecf/src/org/eclipse/equinox/internal/p2/transport/ecf/RepositoryTransport.java b/bundles/org.eclipse.equinox.p2.transport.ecf/src/org/eclipse/equinox/internal/p2/transport/ecf/RepositoryTransport.java new file mode 100644 index 000000000..b7d0b268c --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.transport.ecf/src/org/eclipse/equinox/internal/p2/transport/ecf/RepositoryTransport.java @@ -0,0 +1,279 @@ +/******************************************************************************* + * Copyright (c) 2006, 2010, IBM Corporation and other. + * The code, documentation and other materials contained herein have been + * licensed under the Eclipse Public License - v 1.0 by the copyright holder + * listed above, as the Initial Contributor under such license. The text of + * such license is available at www.eclipse.org. + * + * Contributors + * IBM Corporation - Initial API and implementation. + * Cloudsmith Inc - Implementation + ******************************************************************************/ + +package org.eclipse.equinox.internal.p2.transport.ecf; + +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.ConnectException; +import java.net.URI; +import java.net.UnknownHostException; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.core.runtime.Status; +import org.eclipse.ecf.core.identity.IDCreateException; +import org.eclipse.ecf.core.security.ConnectContextFactory; +import org.eclipse.ecf.core.security.IConnectContext; +import org.eclipse.ecf.filetransfer.BrowseFileTransferException; +import org.eclipse.ecf.filetransfer.IncomingFileTransferException; +import org.eclipse.ecf.filetransfer.UserCancelledException; +import org.eclipse.equinox.internal.p2.repository.AuthenticationFailedException; +import org.eclipse.equinox.internal.p2.repository.Credentials; +import org.eclipse.equinox.internal.p2.repository.Credentials.LoginCanceledException; +import org.eclipse.equinox.internal.p2.repository.DownloadStatus; +import org.eclipse.equinox.internal.p2.repository.FileInfo; +import org.eclipse.equinox.internal.p2.repository.JREHttpClientRequiredException; +import org.eclipse.equinox.internal.p2.repository.Messages; +import org.eclipse.equinox.internal.p2.repository.RepositoryPreferences; +import org.eclipse.equinox.internal.p2.repository.Transport; +import org.eclipse.equinox.internal.provisional.p2.repository.IStateful; +import org.eclipse.equinox.p2.core.IProvisioningAgent; +import org.eclipse.equinox.p2.core.ProvisionException; +import org.eclipse.equinox.p2.core.UIServices.AuthenticationInfo; +import org.eclipse.equinox.p2.core.spi.IAgentServiceFactory; +import org.eclipse.osgi.util.NLS; + +/** + * RepositoryTransport adapts p2 to ECF file download and file browsing. + * Download is performed by {@link FileReader}, and file browsing is performed by + * {@link FileInfoReader}. + */ +public class RepositoryTransport extends Transport implements IAgentServiceFactory { + private static RepositoryTransport instance; + + /** + * Returns an shared instance of Generic Transport + */ + // public static synchronized RepositoryTransport getInstance() { + // if (instance == null) { + // instance = new RepositoryTransport(); + // } + // return instance; + // } + + public IStatus download(URI toDownload, OutputStream target, long startPos, IProgressMonitor monitor) { + + boolean promptUser = false; + boolean useJREHttp = false; + AuthenticationInfo loginDetails = null; + for (int i = RepositoryPreferences.getLoginRetryCount(); i > 0; i--) { + FileReader reader = null; + try { + loginDetails = Credentials.forLocation(toDownload, promptUser, loginDetails); + IConnectContext context = (loginDetails == null) ? null : ConnectContextFactory.createUsernamePasswordConnectContext(loginDetails.getUserName(), loginDetails.getPassword()); + + // perform the download + reader = new FileReader(context); + reader.readInto(toDownload, target, startPos, monitor); + + // check that job ended ok - throw exceptions otherwise + IStatus result = reader.getResult(); + if (result == null) { + String msg = NLS.bind(Messages.RepositoryTransport_failedReadRepo, toDownload); + DownloadStatus ds = new DownloadStatus(IStatus.ERROR, Activator.ID, ProvisionException.REPOSITORY_FAILED_READ, msg, null); + return statusOn(target, ds, reader); + } + if (result.getSeverity() == IStatus.CANCEL) + throw new OperationCanceledException(); + if (!result.isOK()) + throw new CoreException(result); + + // Download status is expected on success + DownloadStatus status = new DownloadStatus(IStatus.OK, Activator.ID, Status.OK_STATUS.getMessage()); + return statusOn(target, status, reader); + } catch (UserCancelledException e) { + statusOn(target, new DownloadStatus(IStatus.CANCEL, Activator.ID, 1, "", null), reader); //$NON-NLS-1$ + throw new OperationCanceledException(); + } catch (OperationCanceledException e) { + statusOn(target, new DownloadStatus(IStatus.CANCEL, Activator.ID, 1, "", null), reader); //$NON-NLS-1$ + throw e; + } catch (CoreException e) { + if (e.getStatus().getException() == null) + return statusOn(target, forException(e, toDownload), reader); + return statusOn(target, forStatus(e.getStatus(), toDownload), reader); + } catch (FileNotFoundException e) { + return statusOn(target, forException(e, toDownload), reader); + } catch (AuthenticationFailedException e) { + promptUser = true; + } catch (Credentials.LoginCanceledException e) { + DownloadStatus status = new DownloadStatus(IStatus.ERROR, Activator.ID, ProvisionException.REPOSITORY_FAILED_AUTHENTICATION, // + NLS.bind(Messages.UnableToRead_0_UserCanceled, toDownload), null); + return statusOn(target, status, null); + } catch (JREHttpClientRequiredException e) { + if (!useJREHttp) { + useJREHttp = true; // only do this once + i++; // need an extra retry + Activator.getDefault().useJREHttpClient(); + } + } + } + // reached maximum number of retries without success + DownloadStatus status = new DownloadStatus(IStatus.ERROR, Activator.ID, ProvisionException.REPOSITORY_FAILED_AUTHENTICATION, // + NLS.bind(Messages.UnableToRead_0_TooManyAttempts, toDownload), null); + return statusOn(target, status, null); + } + + public IStatus download(URI toDownload, OutputStream target, IProgressMonitor monitor) { + return download(toDownload, target, -1, monitor); + } + + public InputStream stream(URI toDownload, IProgressMonitor monitor) throws FileNotFoundException, CoreException, AuthenticationFailedException { + + boolean promptUser = false; + boolean useJREHttp = false; + AuthenticationInfo loginDetails = null; + for (int i = RepositoryPreferences.getLoginRetryCount(); i > 0; i--) { + FileReader reader = null; + try { + loginDetails = Credentials.forLocation(toDownload, promptUser, loginDetails); + IConnectContext context = (loginDetails == null) ? null : ConnectContextFactory.createUsernamePasswordConnectContext(loginDetails.getUserName(), loginDetails.getPassword()); + + // perform the streamed download + reader = new FileReader(context); + return reader.read(toDownload, monitor); + } catch (UserCancelledException e) { + throw new OperationCanceledException(); + } catch (AuthenticationFailedException e) { + promptUser = true; + } catch (CoreException e) { + // must translate this core exception as it is most likely not informative to a user + if (e.getStatus().getException() == null) + throw new CoreException(RepositoryStatus.forException(e, toDownload)); + throw new CoreException(RepositoryStatus.forStatus(e.getStatus(), toDownload)); + } catch (LoginCanceledException e) { + // i.e. same behavior when user cancels as when failing n attempts. + throw new AuthenticationFailedException(); + } catch (JREHttpClientRequiredException e) { + if (!useJREHttp) { + useJREHttp = true; // only do this once + i++; // need an extra retry + Activator.getDefault().useJREHttpClient(); + } + } + } + throw new AuthenticationFailedException(); + } + + /** + * Set the status on the output stream if it implements IStateful. + * Update the DownloadStatus with information from FileReader. + * @param target an OutputStream possibly implementing IStateful + * @param status a DownloadStatus configured with status message, code, etc + * @param reader a FileReade that was used to download (or null if not known). + * @throws OperationCanceledException if the operation was canceled by the user. + * @return the configured DownloadStatus status. + */ + private static DownloadStatus statusOn(OutputStream target, DownloadStatus status, FileReader reader) { + if (reader != null) { + FileInfo fi = reader.getLastFileInfo(); + if (fi != null) { + status.setFileSize(fi.getSize()); + status.setLastModified(fi.getLastModified()); + status.setTransferRate(fi.getAverageSpeed()); + } + } + if (target instanceof IStateful) + ((IStateful) target).setStatus(status); + return status; + } + + public long getLastModified(URI toDownload, IProgressMonitor monitor) throws CoreException, FileNotFoundException, AuthenticationFailedException { + boolean promptUser = false; + boolean useJREHttp = false; + AuthenticationInfo loginDetails = null; + for (int i = RepositoryPreferences.getLoginRetryCount(); i > 0; i--) { + try { + loginDetails = Credentials.forLocation(toDownload, promptUser, loginDetails); + IConnectContext context = (loginDetails == null) ? null : ConnectContextFactory.createUsernamePasswordConnectContext(loginDetails.getUserName(), loginDetails.getPassword()); + // get the remote info + FileInfoReader reader = new FileInfoReader(context); + return reader.getLastModified(toDownload, monitor); + } catch (UserCancelledException e) { + throw new OperationCanceledException(); + } catch (CoreException e) { + // must translate this core exception as it is most likely not informative to a user + if (e.getStatus().getException() == null) + throw new CoreException(RepositoryStatus.forException(e, toDownload)); + throw new CoreException(RepositoryStatus.forStatus(e.getStatus(), toDownload)); + } catch (AuthenticationFailedException e) { + promptUser = true; + } catch (LoginCanceledException e) { + // same behavior as if user failed n attempts. + throw new AuthenticationFailedException(); + } catch (JREHttpClientRequiredException e) { + if (!useJREHttp) { + useJREHttp = true; // only do this once + i++; // need an extra retry + Activator.getDefault().useJREHttpClient(); + } + } + + } + // reached maximum number of authentication retries without success + throw new AuthenticationFailedException(); + } + + public static DownloadStatus forStatus(IStatus original, URI toDownload) { + Throwable t = original.getException(); + return forException(t, toDownload); + } + + public static DownloadStatus forException(Throwable t, URI toDownload) { + if (t instanceof FileNotFoundException || (t instanceof IncomingFileTransferException && ((IncomingFileTransferException) t).getErrorCode() == 404)) + return new DownloadStatus(IStatus.ERROR, Activator.ID, ProvisionException.ARTIFACT_NOT_FOUND, NLS.bind(Messages.artifact_not_found, toDownload), t); + if (t instanceof ConnectException) + return new DownloadStatus(IStatus.ERROR, Activator.ID, ProvisionException.REPOSITORY_FAILED_READ, NLS.bind(Messages.TransportErrorTranslator_UnableToConnectToRepository_0, toDownload), t); + if (t instanceof UnknownHostException) + return new DownloadStatus(IStatus.ERROR, Activator.ID, ProvisionException.REPOSITORY_INVALID_LOCATION, NLS.bind(Messages.TransportErrorTranslator_UnknownHost, toDownload), t); + if (t instanceof IDCreateException) { + IStatus status = ((IDCreateException) t).getStatus(); + if (status != null && status.getException() != null) + t = status.getException(); + + return new DownloadStatus(IStatus.ERROR, Activator.ID, ProvisionException.REPOSITORY_INVALID_LOCATION, NLS.bind(Messages.TransportErrorTranslator_MalformedRemoteFileReference, toDownload), t); + } + int code = 0; + + // default to report as read repository error + int provisionCode = ProvisionException.REPOSITORY_FAILED_READ; + + if (t instanceof IncomingFileTransferException) + code = ((IncomingFileTransferException) t).getErrorCode(); + else if (t instanceof BrowseFileTransferException) + code = ((BrowseFileTransferException) t).getErrorCode(); + + // Switch on error codes in the HTTP error code range. + // Note that 404 uses ARTIFACT_NOT_FOUND (as opposed to REPOSITORY_NOT_FOUND, which + // is determined higher up in the calling chain). + if (code == 401) + provisionCode = ProvisionException.REPOSITORY_FAILED_AUTHENTICATION; + else if (code == 404) + provisionCode = ProvisionException.ARTIFACT_NOT_FOUND; + + // Add more specific translation here + + return new DownloadStatus(IStatus.ERROR, Activator.ID, provisionCode, // + code == 0 ? NLS.bind(Messages.io_failedRead, toDownload) // + : RepositoryStatus.codeToMessage(code, toDownload.toString()), t); + } + + @Override + public Object createService(IProvisioningAgent agent) { + if (instance == null) + return instance; + return instance; + } +} diff --git a/bundles/org.eclipse.equinox.p2.transport.ecf/src/org/eclipse/equinox/internal/p2/transport/ecf/messages.properties b/bundles/org.eclipse.equinox.p2.transport.ecf/src/org/eclipse/equinox/internal/p2/transport/ecf/messages.properties new file mode 100644 index 000000000..4e59267be --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.transport.ecf/src/org/eclipse/equinox/internal/p2/transport/ecf/messages.properties @@ -0,0 +1,76 @@ +############################################################################### +# Copyright (c) 2007, 2010 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 messages +# Sonatype Inc - ongoing implementation +############################################################################### +artifact_not_found=Artifact not found: {0}. + +io_failedRead=Unable to read repository at {0}. +ecf_configuration_error=Transport initialization error. + + +repoMan_internalError=Internal error. +repo_loading = Loading the repository {0} + +CacheManager_Neither_0_nor_1_found=Neither {0} nor {1} found. +CacheManager_AuthenticationFaileFor_0=Authentication failed for {0}. +CacheManager_FailedCommunicationWithRepo_0=Communication with repository at {0} failed. +CacheManage_ErrorRenamingCache=An error occurred while downloading {0}. The cache file {1} could not be renamed to {1}. + +exception_malformedRepoURI = The repository location ({0}) must be a URI. + +TransportErrorTranslator_400=Bad HTTP Request: {0} +TransportErrorTranslator_401=Authentication Failed - Unauthorized: {0} +TransportErrorTranslator_402=HTTP Payment Required: {0} +TransportErrorTranslator_403=HTTP Access Forbidden: {0} +TransportErrorTranslator_404=HTTP Remote File Not Found: {0} +TransportErrorTranslator_405=HTTP Method Not Allowed: {0} +TransportErrorTranslator_406=HTTP Request Not Acceptable: {0} +TransportErrorTranslator_407=HTTP Proxy Authentication Required: {0} +TransportErrorTranslator_408=HTTP Request Timeout: {0} +TransportErrorTranslator_409=HTTP Conflict In Request: {0} +TransportErrorTranslator_410=HTTP Remote File Permanently Removed: {0} +TransportErrorTranslator_411=HTTP Length Required: {0} +TransportErrorTranslator_412=HTTP Precondition Failed: {0} +TransportErrorTranslator_413=HTTP Requested Entity Too Large: {0} +TransportErrorTranslator_414=HTTP Request URI Too Long: {0} +TransportErrorTranslator_415=HTTP Unsupported Media Type: {0} +TransportErrorTranslator_416=HTTP Requested Range Not Satisfiable: {0} +TransportErrorTranslator_417=HTTP Expectation Failed: {0} +TransportErrorTranslator_418=HTTP Cannot provision coffee from a tea pot: {0} +TransportErrorTranslator_422=HTTP (WebDav) Unprocessable Entity: {0} +TransportErrorTranslator_423=HTTP (WebDAV) Locked: {0} +TransportErrorTranslator_424=HTTP (WebDAV) Failed Dependency: {0} +TransportErrorTranslator_425=HTTP Unordered Collection: {0} +TransportErrorTranslator_426=HTTP Upgrade Required: {0} +TransportErrorTranslator_449=HTTP Retry With Response: {0} +TransportErrorTranslator_450=HTTP Blocked By Parental Control: {0} +TransportErrorTranslator_500=HTTP Server ''Internal Error'': {0} +TransportErrorTranslator_501=HTTP Server ''Not Implemented'': {0} +TransportErrorTranslator_502=HTTP Server ''Bad Gateway'' : {0} +TransportErrorTranslator_503=HTTP Server ''Service Unavailable'': {0} +TransportErrorTranslator_504=HTTP Server ''Gateway Timeout'': {0} +TransportErrorTranslator_505=HTTP Server ''HTTP Version Not Supported'': {0} +TransportErrorTranslator_506=HTTP Server ''Variant Also Negotiates'': {0} +TransportErrorTranslator_507=HTTP (WebDAV) ''Insufficient Storage'': {0} +TransportErrorTranslator_508=HTTP Server ''Bandwidth Limit Exceeded'': {0} +TransportErrorTranslator_510=HTTP Server ''Not Extended'': {0} +TransportErrorTranslator_MalformedRemoteFileReference=Malformed reference to remote file: {0} +TransportErrorTranslator_UnableToConnectToRepository_0=Unable to connect to repository {0} +TransportErrorTranslator_UnknownErrorCode=HTTP Server Unknown HTTP Response Code ({0}):{1} +TransportErrorTranslator_UnknownHost=Unknown Host: {0} +fetching_0_from_1_2_at_3=Fetching {0} ({2} at {3}/s) from {1} +fetching_0_from_1_2_of_3_at_4=Fetching {0} ({2} of {3} at {4}/s) from {1} +FileTransport_reader=File Transport Reader +FileTransport_cancelCheck=File Transport Cancel Handler +connection_to_0_failed_on_1_retry_attempt_2=Connection to {0} failed on {1}. Retry attempt {2} started +UnableToRead_0_TooManyAttempts=Unable to read repository at: {0}. Too many failed login attempts. +UnableToRead_0_UserCanceled=Unable to read repository at: {0}. Login canceled by user. +RepositoryTransport_failedReadRepo=Error while reading from repository: {0}. |