blob: a4ceeac7ab0156738428365ea0d9d0f7f3dd3754 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009, 2014 Tasktop Technologies and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* https://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*
* Tasktop Technologies - initial API and implementation
*******************************************************************************/
package org.eclipse.mylyn.internal.discovery.ui;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
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.core.runtime.SubMonitor;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.equinox.internal.p2.ui.ProvUI;
import org.eclipse.equinox.p2.core.ProvisionException;
import org.eclipse.equinox.p2.engine.IProfile;
import org.eclipse.equinox.p2.metadata.IInstallableUnit;
import org.eclipse.equinox.p2.metadata.Version;
import org.eclipse.equinox.p2.operations.InstallOperation;
import org.eclipse.equinox.p2.operations.ProvisioningSession;
import org.eclipse.equinox.p2.operations.RepositoryTracker;
import org.eclipse.equinox.p2.operations.UninstallOperation;
import org.eclipse.equinox.p2.query.IQuery;
import org.eclipse.equinox.p2.query.IQueryResult;
import org.eclipse.equinox.p2.query.QueryUtil;
import org.eclipse.equinox.p2.repository.metadata.IMetadataRepository;
import org.eclipse.equinox.p2.repository.metadata.IMetadataRepositoryManager;
import org.eclipse.equinox.p2.ui.ProvisioningUI;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.mylyn.internal.discovery.core.model.ConnectorDescriptor;
import org.eclipse.mylyn.internal.discovery.ui.util.DiscoveryUiUtil;
import org.eclipse.mylyn.internal.discovery.ui.wizards.Messages;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.widgets.Display;
/**
* A job that configures a p2 {@link #getInstallAction() install action} for installing one or more
* {@link ConnectorDescriptor connectors}. The bulk of the installation work is done by p2; this class just sets up the
* p2 repository meta-data and selects the appropriate features to install. After running the job the
* {@link #getInstallAction() install action} must be run to perform the installation.
*
* @author David Green
* @author Steffen Pingel
*/
class PrepareInstallProfileJob extends AbstractInstallJob {
private final List<ConnectorDescriptor> installableConnectors;
private final ProvisioningUI provisioningUI;
private Set<URI> repositoryLocations;
public PrepareInstallProfileJob(List<ConnectorDescriptor> installableConnectors) {
if (installableConnectors == null) {
throw new IllegalArgumentException();
}
this.installableConnectors = new ArrayList<ConnectorDescriptor>(installableConnectors);
this.provisioningUI = ProvisioningUI.getDefaultUI();
}
public void run(IProgressMonitor progressMonitor) throws InvocationTargetException, InterruptedException {
if (installableConnectors.isEmpty()) {
throw new IllegalArgumentException();
}
try {
SubMonitor monitor = SubMonitor.convert(progressMonitor, Messages.InstallConnectorsJob_task_configuring,
100);
try {
final IInstallableUnit[] ius = computeInstallableUnits(monitor.newChild(50));
checkCancelled(monitor);
final InstallOperation installOperation = resolveInstall(monitor.newChild(50), ius,
repositoryLocations.toArray(new URI[0]));
checkCancelled(monitor);
Display.getDefault().asyncExec(new Runnable() {
public void run() {
provisioningUI.openInstallWizard(Arrays.asList(ius), installOperation, null);
}
});
} finally {
monitor.done();
}
} catch (OperationCanceledException e) {
throw new InterruptedException();
} catch (Exception e) {
throw new InvocationTargetException(e);
}
}
@Override
public IStatus uninstall(UninstallRequest request, IProgressMonitor progressMonitor)
throws InvocationTargetException, InterruptedException {
IProfile profile = ProvUI.getProfileRegistry(ProvisioningUI.getDefaultUI().getSession()).getProfile(
ProvisioningUI.getDefaultUI().getProfileId());
if (profile == null) {
throw new IllegalStateException("No valid profile defined"); //$NON-NLS-1$
}
try {
SubMonitor monitor = SubMonitor.convert(progressMonitor, Messages.InstallConnectorsJob_task_configuring,
100);
try {
repositoryLocations = new HashSet<URI>(Arrays.asList(provisioningUI.getRepositoryTracker()
.getKnownRepositories(provisioningUI.getSession())));
final List<IInstallableUnit> ius = new ArrayList<IInstallableUnit>();
IQueryResult<IInstallableUnit> result = profile.available(QueryUtil.createIUGroupQuery(), monitor);
for (Iterator<IInstallableUnit> it = result.iterator(); it.hasNext();) {
IInstallableUnit iu = it.next();
try {
org.osgi.framework.Version version = new org.osgi.framework.Version(iu.getVersion()
.getOriginal());
InstalledItem<IInstallableUnit> item = new InstalledItem<IInstallableUnit>(iu, iu.getId(),
version);
if (request.select(item)) {
ius.add(iu);
}
} catch (IllegalArgumentException e) {
// ignore
}
}
checkCancelled(monitor);
if (ius.size() == 0) {
return Status.CANCEL_STATUS;
}
final UninstallOperation uninstallOperation = resolveUninstall(monitor.newChild(50),
ius.toArray(new IInstallableUnit[0]), repositoryLocations.toArray(new URI[0]));
checkCancelled(monitor);
return uninstallOperation.getProvisioningJob(null).runModal(monitor);
} finally {
monitor.done();
}
} catch (OperationCanceledException e) {
throw new InterruptedException();
} catch (Exception e) {
throw new InvocationTargetException(e);
}
}
private void checkCancelled(IProgressMonitor monitor) {
if (monitor.isCanceled()) {
throw new OperationCanceledException();
}
}
private UninstallOperation resolveUninstall(IProgressMonitor monitor, final IInstallableUnit[] ius,
URI[] repositories) throws CoreException {
final UninstallOperation uninstallOperation = provisioningUI.getUninstallOperation(Arrays.asList(ius),
repositories);
IStatus operationStatus = uninstallOperation.resolveModal(new SubProgressMonitor(monitor,
installableConnectors.size()));
if (operationStatus.getSeverity() > IStatus.WARNING) {
throw new CoreException(operationStatus);
}
return uninstallOperation;
}
private InstallOperation resolveInstall(IProgressMonitor monitor, final IInstallableUnit[] ius, URI[] repositories)
throws CoreException {
final InstallOperation installOperation = provisioningUI.getInstallOperation(Arrays.asList(ius), repositories);
IStatus operationStatus = installOperation.resolveModal(new SubProgressMonitor(monitor,
installableConnectors.size()));
if (operationStatus.getSeverity() > IStatus.WARNING) {
throw new CoreException(operationStatus);
}
return installOperation;
}
public IInstallableUnit[] computeInstallableUnits(SubMonitor monitor) throws CoreException {
try {
monitor.setWorkRemaining(100);
// add repository urls and load meta data
List<IMetadataRepository> repositories = addRepositories(monitor.newChild(50));
final List<IInstallableUnit> installableUnits = queryInstallableUnits(monitor.newChild(50), repositories);
removeOldVersions(installableUnits);
checkForUnavailable(installableUnits);
return installableUnits.toArray(new IInstallableUnit[installableUnits.size()]);
// MultiStatus status = new MultiStatus(DiscoveryUi.ID_PLUGIN, 0, Messages.PrepareInstallProfileJob_ok, null);
// ius = installableUnits.toArray(new IInstallableUnit[installableUnits.size()]);
// ProfileChangeRequest profileChangeRequest = InstallAction.computeProfileChangeRequest(ius, profileId,
// status, new SubProgressMonitor(monitor, installableConnectors.size()));
// if (status.getSeverity() > IStatus.WARNING) {
// throw new CoreException(status);
// }
// if (profileChangeRequest == null) {
// // failed but no indication as to why
// throw new CoreException(new Status(IStatus.ERROR, DiscoveryUi.ID_PLUGIN,
// Messages.PrepareInstallProfileJob_computeProfileChangeRequestFailed, null));
// }
// PlannerResolutionOperation operation = new PlannerResolutionOperation(
// Messages.PrepareInstallProfileJob_calculatingRequirements, profileId, profileChangeRequest, null,
// status, true);
// IStatus operationStatus = operation.execute(new SubProgressMonitor(monitor, installableConnectors.size()));
// if (operationStatus.getSeverity() > IStatus.WARNING) {
// throw new CoreException(operationStatus);
// }
//
// plannerResolutionOperation = operation;
} catch (URISyntaxException e) {
// should never happen, since we already validated URLs.
throw new CoreException(new Status(IStatus.ERROR, DiscoveryUi.ID_PLUGIN,
Messages.InstallConnectorsJob_unexpectedError_url, e));
} catch (MalformedURLException e) {
// should never happen, since we already validated URLs.
throw new CoreException(new Status(IStatus.ERROR, DiscoveryUi.ID_PLUGIN,
Messages.InstallConnectorsJob_unexpectedError_url, e));
} finally {
monitor.done();
}
}
/**
* Verifies that we found what we were looking for: it's possible that we have connector descriptors that are no
* longer available on their respective sites. In that case we must inform the user. Unfortunately this is the
* earliest point at which we can know.
*/
private void checkForUnavailable(final List<IInstallableUnit> installableUnits) throws CoreException {
// at least one selected connector could not be found in a repository
Set<String> foundIds = new HashSet<String>();
for (IInstallableUnit unit : installableUnits) {
foundIds.add(unit.getId());
}
String message = ""; //$NON-NLS-1$
String detailedMessage = ""; //$NON-NLS-1$
for (ConnectorDescriptor descriptor : installableConnectors) {
StringBuilder unavailableIds = null;
for (String id : descriptor.getInstallableUnits()) {
if (!foundIds.contains(id)) {
if (unavailableIds == null) {
unavailableIds = new StringBuilder();
} else {
unavailableIds.append(Messages.InstallConnectorsJob_commaSeparator);
}
unavailableIds.append(id);
}
}
if (unavailableIds != null) {
if (message.length() > 0) {
message += Messages.InstallConnectorsJob_commaSeparator;
}
message += descriptor.getName();
if (detailedMessage.length() > 0) {
detailedMessage += Messages.InstallConnectorsJob_commaSeparator;
}
detailedMessage += NLS.bind(Messages.PrepareInstallProfileJob_notFoundDescriptorDetail, new Object[] {
descriptor.getName(), unavailableIds.toString(), descriptor.getSiteUrl() });
}
}
if (message.length() > 0) {
// instead of aborting here we ask the user if they wish to proceed anyways
final boolean[] okayToProceed = new boolean[1];
final String finalMessage = message;
Display.getDefault().syncExec(new Runnable() {
public void run() {
okayToProceed[0] = MessageDialog.openQuestion(DiscoveryUiUtil.getShell(),
Messages.InstallConnectorsJob_questionProceed,
NLS.bind(Messages.InstallConnectorsJob_questionProceed_long, new Object[] { finalMessage }));
}
});
if (!okayToProceed[0]) {
throw new CoreException(new Status(IStatus.ERROR, DiscoveryUi.ID_PLUGIN, NLS.bind(
Messages.InstallConnectorsJob_connectorsNotAvailable, detailedMessage), null));
}
}
}
/**
* Filters those installable units that have a duplicate in the list with a higher version number. it's possible
* that some repositories will host multiple versions of a particular feature. we assume that the user wants the
* highest version.
*/
private void removeOldVersions(final List<IInstallableUnit> installableUnits) {
Map<String, Version> symbolicNameToVersion = new HashMap<String, Version>();
for (IInstallableUnit unit : installableUnits) {
Version version = symbolicNameToVersion.get(unit.getId());
if (version == null || version.compareTo(unit.getVersion()) < 0) {
symbolicNameToVersion.put(unit.getId(), unit.getVersion());
}
}
if (symbolicNameToVersion.size() != installableUnits.size()) {
for (IInstallableUnit unit : new ArrayList<IInstallableUnit>(installableUnits)) {
Version version = symbolicNameToVersion.get(unit.getId());
if (!version.equals(unit.getVersion())) {
installableUnits.remove(unit);
}
}
}
}
/**
* Perform a query to get the installable units. This causes p2 to determine what features are available in each
* repository. We select installable units by matching both the feature id and the repository; it is possible though
* unlikely that the same feature id is available from more than one of the selected repositories, and we must
* ensure that the user gets the one that they asked for.
*/
private List<IInstallableUnit> queryInstallableUnits(SubMonitor monitor, List<IMetadataRepository> repositories)
throws URISyntaxException {
final List<IInstallableUnit> installableUnits = new ArrayList<IInstallableUnit>();
monitor.setWorkRemaining(repositories.size());
for (final IMetadataRepository repository : repositories) {
checkCancelled(monitor);
final Set<String> installableUnitIdsThisRepository = getDescriptorIds(repository);
IQuery<IInstallableUnit> query = QueryUtil.createIUGroupQuery();
IQueryResult<IInstallableUnit> result = repository.query(query, monitor.newChild(1));
for (Iterator<IInstallableUnit> iter = result.iterator(); iter.hasNext();) {
IInstallableUnit iu = iter.next();
String id = iu.getId();
if (installableUnitIdsThisRepository.contains(id)) {
installableUnits.add(iu);
}
}
}
return installableUnits;
}
private List<IMetadataRepository> addRepositories(SubMonitor monitor) throws MalformedURLException,
URISyntaxException, ProvisionException {
// tell p2 that it's okay to use these repositories
ProvisioningSession session = ProvisioningUI.getDefaultUI().getSession();
RepositoryTracker repositoryTracker = ProvisioningUI.getDefaultUI().getRepositoryTracker();
repositoryLocations = new HashSet<URI>();
monitor.setWorkRemaining(installableConnectors.size() * 5);
for (ConnectorDescriptor descriptor : installableConnectors) {
URI uri = new URL(descriptor.getSiteUrl()).toURI();
if (repositoryLocations.add(uri)) {
checkCancelled(monitor);
repositoryTracker.addRepository(uri, null, session);
}
monitor.worked(1);
}
// add selected repositories to resolve dependencies
URI[] knownRepositories = repositoryTracker.getKnownRepositories(session);
if (knownRepositories != null) {
for (URI uri : knownRepositories) {
if (Pattern.matches("http://download.eclipse.org/releases/.*", uri.toString())) { //$NON-NLS-1$
repositoryLocations.add(uri);
}
}
}
// fetch meta-data for these repositories
ArrayList<IMetadataRepository> repositories = new ArrayList<IMetadataRepository>();
monitor.setWorkRemaining(repositories.size());
IMetadataRepositoryManager manager = (IMetadataRepositoryManager) session.getProvisioningAgent().getService(
IMetadataRepositoryManager.SERVICE_NAME);
for (URI uri : repositoryLocations) {
checkCancelled(monitor);
IMetadataRepository repository = manager.loadRepository(uri, monitor.newChild(1));
repositories.add(repository);
}
return repositories;
}
private Set<String> getDescriptorIds(final IMetadataRepository repository) throws URISyntaxException {
final Set<String> installableUnitIdsThisRepository = new HashSet<String>();
// determine all installable units for this repository
for (ConnectorDescriptor descriptor : installableConnectors) {
try {
if (repository.getLocation().equals(new URL(descriptor.getSiteUrl()).toURI())) {
installableUnitIdsThisRepository.addAll(descriptor.getInstallableUnits());
}
} catch (MalformedURLException e) {
// will never happen, ignore
}
}
return installableUnitIdsThisRepository;
}
@Override
public Set<String> getInstalledFeatures(IProgressMonitor monitor) {
Set<String> features = new HashSet<String>();
IProfile profile = ProvUI.getProfileRegistry(ProvisioningUI.getDefaultUI().getSession()).getProfile(
ProvisioningUI.getDefaultUI().getProfileId());
if (profile != null) {
IQueryResult<IInstallableUnit> result = profile.available(QueryUtil.createIUGroupQuery(), monitor);
for (Iterator<IInstallableUnit> it = result.iterator(); it.hasNext();) {
IInstallableUnit unit = it.next();
features.add(unit.getId());
}
}
return features;
}
}