blob: cc42fd3b86ebca0b2523c1c45ff78035feddc111 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2008 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
* compeople AG (Stefan Liebig) - various ongoing maintenance
*******************************************************************************/
package org.eclipse.equinox.internal.p2.artifact.repository;
import java.io.*;
import java.net.ProtocolException;
import java.net.URLEncoder;
import org.eclipse.core.runtime.*;
import org.eclipse.ecf.core.security.ConnectContextFactory;
import org.eclipse.ecf.core.security.IConnectContext;
import org.eclipse.ecf.filetransfer.*;
import org.eclipse.ecf.filetransfer.events.*;
import org.eclipse.ecf.filetransfer.identity.FileCreateException;
import org.eclipse.ecf.filetransfer.identity.FileIDFactory;
import org.eclipse.ecf.filetransfer.service.IRetrieveFileTransferFactory;
import org.eclipse.equinox.internal.p2.core.helpers.LogHelper;
import org.eclipse.equinox.internal.provisional.p2.artifact.repository.IStateful;
import org.eclipse.equinox.internal.provisional.p2.core.IServiceUI;
import org.eclipse.equinox.internal.provisional.p2.core.ProvisionException;
import org.eclipse.equinox.internal.provisional.p2.core.IServiceUI.AuthenticationInfo;
import org.eclipse.equinox.internal.provisional.p2.core.repository.IRepository;
import org.eclipse.equinox.security.storage.*;
import org.eclipse.osgi.util.NLS;
import org.osgi.util.tracker.ServiceTracker;
/**
* A transport implementation that uses ECF file transfer API.
*/
public class ECFTransport extends Transport {
/**
* The number of password retry attempts allowed before failing.
*/
private static final int LOGIN_RETRIES = 3;
private static final ProtocolException ERROR_401 = new ProtocolException();
private static final String SERVER_REDIRECT = "Server redirected too many times"; //$NON-NLS-1$
/**
* The singleton transport instance.
*/
private static ECFTransport instance;
private final ServiceTracker retrievalFactoryTracker;
/**
* Returns an initialized instance of ECFTransport
*/
public static synchronized ECFTransport getInstance() {
if (instance == null) {
instance = new ECFTransport();
}
return instance;
}
/**
* Private to avoid client instantiation.
*/
private ECFTransport() {
retrievalFactoryTracker = new ServiceTracker(Activator.getContext(), IRetrieveFileTransferFactory.class.getName(), null);
retrievalFactoryTracker.open();
}
protected IStatus convertToStatus(IFileTransferEvent event, Exception failure, long startTime, String location) {
long speed = DownloadStatus.UNKNOWN_RATE;
if (event instanceof IIncomingFileTransferEvent) {
long bytes = ((IIncomingFileTransferEvent) event).getSource().getBytesReceived();
if (bytes > 0) {
long elapsed = (System.currentTimeMillis() - startTime) / 1000;//in seconds
if (elapsed == 0)
elapsed = 1;
speed = bytes / elapsed;
}
}
DownloadStatus result = null;
if (failure == null)
result = new DownloadStatus(IStatus.OK, Activator.ID, Status.OK_STATUS.getMessage());
else if (failure instanceof UserCancelledException)
result = new DownloadStatus(IStatus.CANCEL, Activator.ID, failure.getMessage(), failure);
else
result = new DownloadStatus(IStatus.ERROR, Activator.ID, NLS.bind(Messages.io_failedRead, location), failure);
result.setTransferRate(speed);
return result;
}
public IStatus download(String url, OutputStream destination, IProgressMonitor monitor) {
try {
IConnectContext context = getConnectionContext(url, false);
for (int i = 0; i < LOGIN_RETRIES; i++) {
try {
return performDownload(url, destination, context, monitor);
} catch (ProtocolException e) {
if (e == ERROR_401)
context = getConnectionContext(url, true);
}
}
} catch (UserCancelledException e) {
return Status.CANCEL_STATUS;
} catch (ProvisionException e) {
return e.getStatus();
}
//reached maximum number of retries without success
return new Status(IStatus.ERROR, Activator.ID, ProvisionException.REPOSITORY_FAILED_AUTHENTICATION, NLS.bind(Messages.io_failedRead, url), null);
}
public IStatus performDownload(String toDownload, OutputStream target, IConnectContext context, IProgressMonitor monitor) throws ProtocolException {
IRetrieveFileTransferFactory factory = (IRetrieveFileTransferFactory) retrievalFactoryTracker.getService();
if (factory == null)
return statusOn(target, new Status(IStatus.ERROR, Activator.ID, NLS.bind(Messages.io_failedRead, toDownload)));
return transfer(factory.newInstance(), toDownload, target, context, monitor);
}
private IStatus transfer(final IRetrieveFileTransferContainerAdapter retrievalContainer, final String toDownload, final OutputStream target, IConnectContext context, final IProgressMonitor monitor) throws ProtocolException {
final IStatus[] result = new IStatus[1];
final long startTime = System.currentTimeMillis();
IFileTransferListener listener = new IFileTransferListener() {
public void handleTransferEvent(IFileTransferEvent event) {
if (event instanceof IIncomingFileTransferReceiveStartEvent) {
IIncomingFileTransferReceiveStartEvent rse = (IIncomingFileTransferReceiveStartEvent) event;
try {
if (target != null) {
rse.receive(target);
}
} catch (IOException e) {
IStatus status = convertToStatus(event, e, startTime, toDownload);
synchronized (result) {
result[0] = status;
result.notify();
}
}
}
if (event instanceof IIncomingFileTransferReceiveDataEvent) {
IIncomingFileTransfer source = ((IIncomingFileTransferReceiveDataEvent) event).getSource();
if (monitor != null) {
if (monitor.isCanceled()) {
synchronized (result) {
result[0] = Status.CANCEL_STATUS;
source.cancel();
result.notify();
}
}
}
}
if (event instanceof IIncomingFileTransferReceiveDoneEvent) {
Exception exception = ((IIncomingFileTransferReceiveDoneEvent) event).getException();
IStatus status = convertToStatus(event, exception, startTime, toDownload);
synchronized (result) {
result[0] = status;
result.notify();
}
}
}
};
try {
retrievalContainer.setConnectContextForAuthentication(context);
retrievalContainer.sendRetrieveRequest(FileIDFactory.getDefault().createFileID(retrievalContainer.getRetrieveNamespace(), toDownload), listener, null);
} catch (IncomingFileTransferException e) {
IStatus status = e.getStatus();
Throwable exception = status.getException();
if (exception instanceof IOException) {
if (exception.getMessage() != null && (exception.getMessage().indexOf(" 401 ") != -1 || exception.getMessage().indexOf(SERVER_REDIRECT) != -1)) //$NON-NLS-1$
throw ERROR_401;
}
return statusOn(target, status);
} catch (FileCreateException e) {
return statusOn(target, e.getStatus());
}
synchronized (result) {
while (result[0] == null) {
boolean logged = false;
try {
result.wait();
} catch (InterruptedException e) {
if (!logged)
LogHelper.log(new Status(IStatus.WARNING, Activator.ID, "Unexpected interrupt while waiting on ECF transfer", e)); //$NON-NLS-1$
}
}
}
return statusOn(target, result[0]);
}
/**
* Returns the connection context for the given URL. This may prompt the
* user for user name and password as required.
*
* @param xmlLocation - the file location requiring login details
* @param prompt - use <code>true</code> to prompt the user instead of
* looking at the secure preference store for login, use <code>false</code>
* to only try the secure preference store
* @throws UserCancelledException when the user cancels the login prompt
* @throws ProvisionException if the password cannot be read or saved
* @return The connection context
*/
public IConnectContext getConnectionContext(String xmlLocation, boolean prompt) throws UserCancelledException, ProvisionException {
ISecurePreferences securePreferences = SecurePreferencesFactory.getDefault();
IPath hostLocation = new Path(xmlLocation).removeLastSegments(1);
String nodeKey;
try {
nodeKey = URLEncoder.encode(hostLocation.toString(), "UTF-8"); //$NON-NLS-1$
} catch (UnsupportedEncodingException e2) {
//fall back to default platform encoding
nodeKey = URLEncoder.encode(hostLocation.toString());
}
String nodeName = IRepository.PREFERENCE_NODE + '/' + nodeKey;
ISecurePreferences prefNode = null;
if (securePreferences.nodeExists(nodeName))
prefNode = securePreferences.node(nodeName);
if (!prompt) {
if (prefNode == null)
return null;
try {
String username = prefNode.get(IRepository.PROP_USERNAME, null);
String password = prefNode.get(IRepository.PROP_PASSWORD, null);
//if we don't have stored connection data just return a null connection context
if (username == null || password == null)
return null;
return ConnectContextFactory.createUsernamePasswordConnectContext(username, password);
} catch (StorageException e) {
String msg = NLS.bind(Messages.repoMan_internalError, xmlLocation.toString());
throw new ProvisionException(new Status(IStatus.ERROR, Activator.ID, ProvisionException.INTERNAL_ERROR, msg, null));
}
}
//need to prompt user for user name and password
ServiceTracker adminUITracker = new ServiceTracker(Activator.getContext(), IServiceUI.class.getName(), null);
adminUITracker.open();
IServiceUI adminUIService = (IServiceUI) adminUITracker.getService();
AuthenticationInfo loginDetails = null;
if (adminUIService != null)
loginDetails = adminUIService.getUsernamePassword(hostLocation.toString());
//null result means user canceled password dialog
if (loginDetails == null)
throw new UserCancelledException();
//save user name and password if requested by user
if (loginDetails.saveResult()) {
if (prefNode == null)
prefNode = securePreferences.node(nodeName);
try {
prefNode.put(IRepository.PROP_USERNAME, loginDetails.getUserName(), true);
prefNode.put(IRepository.PROP_PASSWORD, loginDetails.getPassword(), true);
prefNode.flush();
} catch (StorageException e1) {
String msg = NLS.bind(Messages.repoMan_internalError, xmlLocation.toString());
throw new ProvisionException(new Status(IStatus.ERROR, Activator.ID, ProvisionException.INTERNAL_ERROR, msg, null));
} catch (IOException e) {
String msg = NLS.bind(Messages.repoMan_internalError, xmlLocation.toString());
throw new ProvisionException(new Status(IStatus.ERROR, Activator.ID, ProvisionException.INTERNAL_ERROR, msg, null));
}
}
return ConnectContextFactory.createUsernamePasswordConnectContext(loginDetails.getUserName(), loginDetails.getPassword());
}
private static IStatus statusOn(OutputStream target, IStatus status) {
if (target instanceof IStateful)
((IStateful) target).setStatus(status);
return status;
}
}