/*******************************************************************************
* Copyright (c) 2009 Tasktop Technologies and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Tasktop Technologies - initial API and implementation
* Obeo - adaptation for Amalgamation, EMF based and no Mylyn dependency
* CEA LIST - adaptation to Papyrus
*******************************************************************************/
package org.eclipse.papyrus.infra.discovery.ui.internal.common;
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.Collection;
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 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.p2.core.ProvisionException;
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.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.jface.operation.IRunnableWithProgress;
import org.eclipse.osgi.util.NLS;
import org.eclipse.papyrus.infra.discovery.InstallableComponent;
import org.eclipse.papyrus.infra.discovery.ui.Activator;
import org.eclipse.papyrus.infra.discovery.ui.internal.wizards.Messages;
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 metadata 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
*/
public class PrepareInstallProfileJob implements IRunnableWithProgress {
private static final String P2_FEATURE_GROUP_SUFFIX = ".feature.group"; //$NON-NLS-1$
private final List<InstallableComponent> installableConnectors;
private final ProvisioningUI provisioningUI;
private Set<URI> repositoryLocations;
public PrepareInstallProfileJob(
Collection<InstallableComponent> installableConnectors) {
if (installableConnectors == null || installableConnectors.isEmpty()) {
throw new IllegalArgumentException();
}
this.installableConnectors = new ArrayList<InstallableComponent>(
installableConnectors);
this.provisioningUI = ProvisioningUI.getDefaultUI();
}
public void run(IProgressMonitor progressMonitor)
throws InvocationTargetException, InterruptedException {
try {
SubMonitor monitor = SubMonitor.convert(progressMonitor,
Messages.InstallConnectorsJob_task_configuring, 100);
try {
final Collection<IInstallableUnit> ius = computeInstallableUnits(monitor
.newChild(50));
checkCancelled(monitor);
final InstallOperation installOperation = resolve(monitor
.newChild(50), ius, repositoryLocations
.toArray(new URI[0]));
checkCancelled(monitor);
Display.getDefault().asyncExec(new Runnable() {
public void run() {
provisioningUI.openInstallWizard(ius, installOperation,
null);
}
});
} 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 InstallOperation resolve(IProgressMonitor monitor,
final Collection<IInstallableUnit> ius, URI[] repositories)
throws CoreException {
final InstallOperation installOperation = provisioningUI
.getInstallOperation(ius, repositories);
IStatus operationStatus = installOperation
.resolveModal(new SubProgressMonitor(monitor,
installableConnectors.size()));
if (operationStatus.getSeverity() > IStatus.WARNING) {
throw new CoreException(operationStatus);
}
return installOperation;
}
public Collection<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;
} catch (URISyntaxException e) {
// should never happen, since we already validated URLs.
throw new CoreException(new Status(IStatus.ERROR,
Activator.PLUGIN_ID,
Messages.InstallConnectorsJob_unexpectedError_url, e));
} catch (MalformedURLException e) {
// should never happen, since we already validated URLs.
throw new CoreException(new Status(IStatus.ERROR,
Activator.PLUGIN_ID,
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) {
String id = unit.getId();
if (id.endsWith(P2_FEATURE_GROUP_SUFFIX)) {
id = id.substring(0, id.indexOf(P2_FEATURE_GROUP_SUFFIX));
}
foundIds.add(id);
}
String message = ""; //$NON-NLS-1$
String detailedMessage = ""; //$NON-NLS-1$
for (InstallableComponent descriptor : installableConnectors) {
StringBuilder unavailableIds = null;
for (String id : descriptor.getId()) {
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.getSitesURLS() });
}
}
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,
Activator.PLUGIN_ID,
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()) == -1) {
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.createMatchQuery( //
"id ~= /*.feature.group/ && " + //$NON-NLS-1$
"properties['org.eclipse.equinox.p2.type.group'] == true ");//$NON-NLS-1$
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.substring(0,
id.indexOf(P2_FEATURE_GROUP_SUFFIX)))) {
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 (InstallableComponent descriptor : installableConnectors) {
for (String url : descriptor.getSitesURLS()) {
addASiteURL(monitor, session, repositoryTracker, url);
}
}
// 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 void addASiteURL(SubMonitor monitor, ProvisioningSession session,
RepositoryTracker repositoryTracker, String url)
throws URISyntaxException, MalformedURLException {
URI uri = new URL(url).toURI();
if (repositoryLocations.add(uri)) {
checkCancelled(monitor);
repositoryTracker.addRepository(uri, null, session);
// ProvisioningUtil.addMetaDataRepository(url.toURI(), true);
// ProvisioningUtil.addArtifactRepository(url.toURI(), true);
// ProvisioningUtil.setColocatedRepositoryEnablement(url.toURI(),
// true);
}
monitor.worked(1);
}
private Set<String> getDescriptorIds(final IMetadataRepository repository)
throws URISyntaxException {
final Set<String> installableUnitIdsThisRepository = new HashSet<String>();
// determine all installable units for this repository
for (InstallableComponent descriptor : installableConnectors) {
try {
if (hasThisUpdateSite(repository.getLocation(), descriptor)) {
installableUnitIdsThisRepository.addAll(descriptor.getId());
}
} catch (MalformedURLException e) {
// will never happen, ignore
}
}
return installableUnitIdsThisRepository;
}
private boolean hasThisUpdateSite(URI location,
InstallableComponent descriptor) throws MalformedURLException {
boolean found = false;
Iterator<String> it = descriptor.getSitesURLS().iterator();
while (it.hasNext() && !found) {
String url = it.next();
if (location.toString().equals(url))
found = true;
}
return found;
}
}