/******************************************************************************* * Copyright (c) 2009 Tasktop Technologies, Polarion Software 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 *******************************************************************************/ package org.eclipse.team.svn.ui.discovery; import java.lang.reflect.InvocationTargetException; import java.net.InetAddress; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; 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.MultiStatus; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.SubProgressMonitor; import org.eclipse.equinox.internal.provisional.p2.core.Version; import org.eclipse.equinox.internal.provisional.p2.director.ProfileChangeRequest; import org.eclipse.equinox.internal.provisional.p2.engine.IProfile; import org.eclipse.equinox.internal.provisional.p2.engine.IProfileRegistry; import org.eclipse.equinox.internal.provisional.p2.metadata.IInstallableUnit; import org.eclipse.equinox.internal.provisional.p2.metadata.IProvidedCapability; import org.eclipse.equinox.internal.provisional.p2.metadata.repository.IMetadataRepository; import org.eclipse.equinox.internal.provisional.p2.query.Collector; import org.eclipse.equinox.internal.provisional.p2.query.MatchQuery; import org.eclipse.equinox.internal.provisional.p2.query.Query; import org.eclipse.equinox.internal.provisional.p2.ui.IProvHelpContextIds; import org.eclipse.equinox.internal.provisional.p2.ui.QueryableMetadataRepositoryManager; import org.eclipse.equinox.internal.provisional.p2.ui.actions.InstallAction; import org.eclipse.equinox.internal.provisional.p2.ui.dialogs.PreselectedIUInstallWizard; import org.eclipse.equinox.internal.provisional.p2.ui.dialogs.ProvisioningWizardDialog; import org.eclipse.equinox.internal.provisional.p2.ui.operations.PlannerResolutionOperation; import org.eclipse.equinox.internal.provisional.p2.ui.operations.ProvisioningUtil; import org.eclipse.equinox.internal.provisional.p2.ui.policy.Policy; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.wizard.WizardDialog; import org.eclipse.swt.widgets.Display; import org.eclipse.team.svn.core.SVNTeamPlugin; import org.eclipse.team.svn.core.discovery.model.ConnectorDescriptor; import org.eclipse.team.svn.ui.SVNUIMessages; import org.eclipse.team.svn.ui.utility.UIMonitorUtility; import org.eclipse.ui.PlatformUI; /** * Install job for Eclipse 3.5 * * 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 Igor Burilo */ @SuppressWarnings("restriction") public class PrepareInstallProfileJob_3_5 implements IConnectorsInstallJob { private static final String P2_FEATURE_GROUP_SUFFIX = ".feature.group"; //$NON-NLS-1$ private List installableConnectors; private PlannerResolutionOperation plannerResolutionOperation; private String profileId; private IInstallableUnit[] ius; private InstallAction installAction; public PrepareInstallProfileJob_3_5() { } public void setInstallableConnectors(List installableConnectors) { this.installableConnectors = installableConnectors; } public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { try { if (this.installableConnectors == null || this.installableConnectors.isEmpty()) { throw new IllegalArgumentException(); } doRun(monitor); if (monitor.isCanceled()) { throw new InterruptedException(); } doInstall(); } catch (OperationCanceledException e) { throw new InterruptedException(); } catch (Exception e) { throw new InvocationTargetException(e); } } protected void doInstall() { if (this.getPlannerResolutionOperation() != null && this.getPlannerResolutionOperation().getProvisioningPlan() != null) { UIMonitorUtility.getDisplay().asyncExec(new Runnable() { public void run() { PreselectedIUInstallWizard wizard = new PreselectedIUInstallWizard(Policy.getDefault(), getProfileId(), getIUs(), getPlannerResolutionOperation(), new QueryableMetadataRepositoryManager(Policy.getDefault().getQueryContext(), false)); WizardDialog dialog = new ProvisioningWizardDialog(UIMonitorUtility.getShell(), wizard); dialog.create(); PlatformUI.getWorkbench().getHelpSystem().setHelp(dialog.getShell(), IProvHelpContextIds.INSTALL_WIZARD); dialog.open(); } }); } } protected void doRun(IProgressMonitor monitor) throws CoreException { final int totalWork = installableConnectors.size() * 6; monitor.beginTask(SVNUIMessages.InstallConnectorsJob_task_configuring, totalWork); try { profileId = computeProfileId(); // verify that we can resolve hostnames // this is a pre-emptive check so that we can provide better error handling // than that provided by P2. { Set hostnames = new HashSet(); for (ConnectorDescriptor descriptor : installableConnectors) { URL url = new URL(descriptor.getSiteUrl()); String host = url.getHost(); if (host != null && host.length() > 0 && hostnames.add(host)) { try { InetAddress.getByName(host); } catch (UnknownHostException e) { throw new CoreException(new Status(IStatus.ERROR, SVNTeamPlugin.NATURE_ID, SVNUIMessages.format(SVNUIMessages.PrepareInstallProfileJob_errorResolvingHostname, new Object[]{descriptor.getName(), host}), e)); } } } } // Tell p2 that it's okay to use these repositories Set repositoryURLs = new HashSet(); for (ConnectorDescriptor descriptor : installableConnectors) { URL url = new URL(descriptor.getSiteUrl()); if (repositoryURLs.add(url)) { if (monitor.isCanceled()) { return; } ProvisioningUtil.addMetadataRepository(url.toURI(), true); ProvisioningUtil.addArtifactRepository(url.toURI(), true); ProvisioningUtil.setColocatedRepositoryEnablement(url.toURI(), true); } monitor.worked(1); } if (repositoryURLs.isEmpty()) { // should never happen throw new IllegalStateException(); } // Fetch p2's metadata for these repositories List repositories = new ArrayList(); final Map repositoryToURL = new HashMap(); { int unit = installableConnectors.size() / repositoryURLs.size(); for (URL updateSiteUrl : repositoryURLs) { if (monitor.isCanceled()) { return; } IMetadataRepository repository = ProvisioningUtil.loadMetadataRepository(updateSiteUrl.toURI(), new SubProgressMonitor(monitor, unit)); repositories.add(repository); repositoryToURL.put(repository, updateSiteUrl); } } // 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. final List installableUnits = new ArrayList(); { int unit = installableConnectors.size() / repositories.size(); for (final IMetadataRepository repository : repositories) { if (monitor.isCanceled()) { return; } URL repositoryUrl = repositoryToURL.get(repository); final Set installableUnitIdsThisRepository = new HashSet(); // determine all installable units for this repository for (ConnectorDescriptor descriptor : installableConnectors) { try { if (repositoryUrl.equals(new URL(descriptor.getSiteUrl()))) { for (String featureId : descriptor.getInstallableUnits()) { installableUnitIdsThisRepository.add(featureId); } } } catch (MalformedURLException e) { // will never happen, ignore } } Collector collector = new Collector(); Query query = new MatchQuery() { @Override public boolean isMatch(Object object) { if (!(object instanceof IInstallableUnit)) { return false; } IInstallableUnit candidate = (IInstallableUnit) object; if ("true".equalsIgnoreCase(candidate.getProperty("org.eclipse.equinox.p2.type.group"))) { //$NON-NLS-1$ //$NON-NLS-2$ String id = candidate.getId(); if (isQualifyingFeature(installableUnitIdsThisRepository, id)) { IProvidedCapability[] providedCapabilities = candidate.getProvidedCapabilities(); if (providedCapabilities != null && providedCapabilities.length > 0) { for (IProvidedCapability capability : providedCapabilities) { if ("org.eclipse.equinox.p2.iu".equals(capability.getNamespace())) { //$NON-NLS-1$ String name = capability.getName(); if (isQualifyingFeature(installableUnitIdsThisRepository, name)) { return true; } } } } } } return false; } private boolean isQualifyingFeature(final Set installableUnitIdsThisRepository, String id) { return id.endsWith(P2_FEATURE_GROUP_SUFFIX) && installableUnitIdsThisRepository.contains(id.substring(0, id.indexOf(P2_FEATURE_GROUP_SUFFIX))); } }; repository.query(query, collector, new SubProgressMonitor(monitor, unit)); addAll(installableUnits, collector); } } // filter 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. Map symbolicNameToVersion = new HashMap(); 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(installableUnits)) { Version version = symbolicNameToVersion.get(unit.getId()); if (!version.equals(unit.getVersion())) { installableUnits.remove(unit); } } } // Verify 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) HashSet features = new HashSet(); for (ConnectorDescriptor cd : installableConnectors) { features.addAll(cd.getInstallableUnits()); } int expectedFeaturesCount = features.size(); if (installableUnits.size() < expectedFeaturesCount) { // at least one selected connector could not be found in a repository Set foundIds = new HashSet(); 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); } final String notFound; String temp = ""; //$NON-NLS-1$ for (ConnectorDescriptor descriptor : installableConnectors) { if (!foundIds.containsAll(descriptor.getInstallableUnits())) { if (temp.length() > 0) { temp += SVNUIMessages.InstallConnectorsJob_commaSeparator; } temp += descriptor.getName(); } } notFound = temp; boolean proceed = false; if (!installableUnits.isEmpty()) { // instead of aborting here we ask the user if they wish to proceed anyways final boolean[] okayToProceed = new boolean[1]; Display.getDefault().syncExec(new Runnable() { public void run() { okayToProceed[0] = MessageDialog.openQuestion(UIMonitorUtility.getShell(), SVNUIMessages.InstallConnectorsJob_questionProceed, SVNUIMessages.format( SVNUIMessages.InstallConnectorsJob_questionProceed_long, new Object[] { notFound })); } }); proceed = okayToProceed[0]; } if (!proceed) { String notFoundDescription = ""; //$NON-NLS-1$ for (ConnectorDescriptor descriptor : installableConnectors) { if (!foundIds.contains(descriptor.getInstallableUnits())) { if (notFoundDescription.length() > 0) { notFoundDescription += SVNUIMessages.InstallConnectorsJob_commaSeparator; } notFoundDescription += SVNUIMessages.format(SVNUIMessages.PrepareInstallProfileJob_notFoundDescriptorDetail, new Object[] { descriptor.getName(), descriptor.getInstallableUnits(), descriptor.getSiteUrl() }); } } throw new CoreException(new Status(IStatus.ERROR, SVNTeamPlugin.NATURE_ID, SVNUIMessages.format( SVNUIMessages.InstallConnectorsJob_connectorsNotAvailable, notFoundDescription), null)); } } else if (installableUnits.size() > expectedFeaturesCount) { // should never ever happen throw new IllegalStateException(); } MultiStatus status = new MultiStatus(SVNTeamPlugin.NATURE_ID, 0, SVNUIMessages.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, SVNTeamPlugin.NATURE_ID, SVNUIMessages.PrepareInstallProfileJob_computeProfileChangeRequestFailed, null)); } PlannerResolutionOperation operation = new PlannerResolutionOperation( SVNUIMessages.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, SVNTeamPlugin.NATURE_ID, SVNUIMessages.InstallConnectorsJob_unexpectedError_url, e)); } catch (MalformedURLException e) { // should never happen, since we already validated URLs. throw new CoreException(new Status(IStatus.ERROR, SVNTeamPlugin.NATURE_ID, SVNUIMessages.InstallConnectorsJob_unexpectedError_url, e)); } finally { monitor.done(); } } @SuppressWarnings("unchecked") private boolean addAll(final List installableUnits, Collector collector) { return installableUnits.addAll(collector.toCollection()); } private String computeProfileId() throws CoreException { IProfile profile = ProvisioningUtil.getProfile(IProfileRegistry.SELF); if (profile != null) { return profile.getProfileId(); } IProfile[] profiles = ProvisioningUtil.getProfiles(); if (profiles.length > 0) { return profiles[0].getProfileId(); } throw new CoreException(new Status(IStatus.ERROR, SVNTeamPlugin.NATURE_ID, SVNUIMessages.InstallConnectorsJob_profileProblem, null)); } public PlannerResolutionOperation getPlannerResolutionOperation() { return plannerResolutionOperation; } public String getProfileId() { return profileId; } public IInstallableUnit[] getIUs() { return ius; } public InstallAction getInstallAction() { return installAction; } }