blob: 45c135df89fa1f4ba0623bd885b72e37f196faa3 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009-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
* Sonatype, Inc. - ongoing development
******************************************************************************/
package org.eclipse.equinox.p2.operations;
import java.util.*;
import org.eclipse.core.runtime.*;
import org.eclipse.equinox.internal.p2.core.helpers.CollectionUtils;
import org.eclipse.equinox.internal.p2.operations.*;
import org.eclipse.equinox.internal.provisional.p2.director.ProfileChangeRequest;
import org.eclipse.equinox.p2.engine.IProfile;
import org.eclipse.equinox.p2.engine.query.UserVisibleRootQuery;
import org.eclipse.equinox.p2.metadata.IInstallableUnit;
import org.eclipse.equinox.p2.planner.ProfileInclusionRules;
import org.eclipse.equinox.p2.query.*;
/**
* An UpdateOperation describes an operation that updates {@link IInstallableUnit}s in
* a profile.
*
* The following snippet shows how one might use an UpdateOperation to check for updates
* to the profile and then install them in the background.
*
* <pre>
* UpdateOperation op = new UpdateOperation(session);
* IStatus result = op.resolveModal(monitor);
* if (result.isOK()) {
* op.getProvisioningJob(monitor).schedule();
* }
* </pre>
*
* The life cycle of an UpdateOperation is different than that of the other
* operations. Since assembling the list of possible updates may be costly,
* clients should not have to create a new update operation if the desired updates
* to be applied need to change. In this case, the client can set a new set of
* chosen updates on the update operation and resolve again.
*
* <pre>
* UpdateOperation op = new UpdateOperation(session);
* IStatus result = op.resolveModal(monitor);
* if (result.isOK()) {
* op.getProvisioningJob(monitor).schedule();
* } else if (result.getSeverity() == IStatus.ERROR) {
* Update [] chosenUpdates = letUserPickFrom(op.getPossibleUpdates());
* op.setSelectedUpdates(chosenUpdates);
* IStatus result = op.resolveModal(monitor);
* }
* </pre>
*
* @noextend This class is not intended to be subclassed by clients.
* @since 2.0
*/
public class UpdateOperation extends ProfileChangeOperation {
/**
* A status code used to indicate that there were no updates found when
* looking for updates.
*/
public static final int STATUS_NOTHING_TO_UPDATE = IStatusCodes.NOTHING_TO_UPDATE;
private Collection<IInstallableUnit> iusToUpdate;
private HashMap<IInstallableUnit, List<Update>> possibleUpdatesByIU = new HashMap<IInstallableUnit, List<Update>>();
private List<Update> defaultUpdates;
/**
* Create an update operation on the specified provisioning session that updates
* the specified IInstallableUnits. Unless otherwise specified, the operation will
* be associated with the currently running profile.
*
* @param session the session to use for obtaining provisioning services
* @param toBeUpdated the IInstallableUnits to be updated.
*/
public UpdateOperation(ProvisioningSession session, Collection<IInstallableUnit> toBeUpdated) {
super(session);
this.iusToUpdate = toBeUpdated;
}
/**
* Create an update operation that will update all of the user-visible installable
* units in the profile (the profile roots).
*
* @param session the session providing the provisioning services
*/
public UpdateOperation(ProvisioningSession session) {
this(session, null);
}
/**
* Set the updates that should be selected from the set of available updates.
* If the selected updates are not specified, then the latest available update
* for each IInstallableUnit with updates will be chosen.
*
* @param defaultUpdates the updates that should be chosen from all of the available
* updates.
*/
public void setSelectedUpdates(Update[] defaultUpdates) {
this.defaultUpdates = new ArrayList<Update>(Arrays.asList(defaultUpdates));
}
/**
* Get the updates that have been selected from the set of available updates.
* If none have been specified by the client, then the latest available update
* for each IInstallableUnit with updates will be chosen.
*
* @return the updates that should be chosen from all of the available updates
*/
public Update[] getSelectedUpdates() {
if (defaultUpdates == null)
return new Update[0];
return defaultUpdates.toArray(new Update[defaultUpdates.size()]);
}
/**
* Get the list of all possible updates. This list may include multiple versions
* of updates for the same IInstallableUnit, as well as patches to the IInstallableUnit.
*
* @return an array of all possible updates
*/
public Update[] getPossibleUpdates() {
ArrayList<Update> all = new ArrayList<Update>();
for (List<Update> updates : possibleUpdatesByIU.values())
all.addAll(updates);
return all.toArray(new Update[all.size()]);
}
private Update[] updatesFor(IInstallableUnit iu, IProfile profile, IProgressMonitor monitor) {
List<Update> updates;
if (possibleUpdatesByIU.containsKey(iu)) {
// We've already looked them up in the planner, use the cache
updates = possibleUpdatesByIU.get(iu);
} else {
// We must consult the planner
IQueryResult<IInstallableUnit> replacements = session.getPlanner().updatesFor(iu, context, monitor);
updates = new ArrayList<Update>();
for (Iterator<IInstallableUnit> replacementIterator = replacements.iterator(); replacementIterator.hasNext();) {
// see https://bugs.eclipse.org/bugs/show_bug.cgi?id=273967
// In the case of patches, it's possible that a patch is returned as an available update
// even though it is already installed, because we are querying each IU for updates individually.
// For now, we ignore any proposed update that is already installed.
IInstallableUnit replacementIU = replacementIterator.next();
IQueryResult<IInstallableUnit> alreadyInstalled = profile.query(QueryUtil.createIUQuery(replacementIU), null);
if (alreadyInstalled.isEmpty()) {
Update update = new Update(iu, replacementIU);
updates.add(update);
}
}
possibleUpdatesByIU.put(iu, updates);
}
return updates.toArray(new Update[updates.size()]);
}
/* (non-Javadoc)
* @see org.eclipse.equinox.p2.operations.ProfileChangeOperation#computeProfileChangeRequest(org.eclipse.core.runtime.IProgressMonitor)
*/
protected void computeProfileChangeRequest(MultiStatus status, IProgressMonitor monitor) {
// Here we create a profile change request by finding the latest version available for any replacement, unless
// otherwise specified in the selections.
// We have to consider the scenario where the only updates available are patches, in which case the original
// IU should not be removed as part of the update.
Set<IInstallableUnit> toBeUpdated = new HashSet<IInstallableUnit>();
HashSet<Update> elementsToPlan = new HashSet<Update>();
boolean selectionSpecified = defaultUpdates != null;
IProfile profile = session.getProfileRegistry().getProfile(profileId);
if (profile == null)
return;
SubMonitor sub = SubMonitor.convert(monitor, Messages.UpdateOperation_ProfileChangeRequestProgress, 100 * iusToUpdate.size());
for (IInstallableUnit iuToUpdate : iusToUpdate) {
SubMonitor iuMon = sub.newChild(100);
Update[] updates = updatesFor(iuToUpdate, profile, iuMon);
for (int j = 0; j < updates.length; j++) {
toBeUpdated.add(iuToUpdate);
if (defaultUpdates != null && defaultUpdates.contains(updates[j])) {
elementsToPlan.add(updates[j]);
}
}
if (!selectionSpecified) {
// If no selection was specified, we must figure out the latest version to apply.
// The rules are that a true update will always win over a patch, but if only
// patches are available, they should all be selected.
// We first gather the latest versions of everything proposed.
// Patches are keyed by their id because they are unique and should not be compared to
// each other. Updates are keyed by the IU they are updating so we can compare the
// versions and select the latest one
HashMap<String, Update> latestVersions = new HashMap<String, Update>();
boolean foundUpdate = false;
boolean foundPatch = false;
for (int j = 0; j < updates.length; j++) {
String key;
if (QueryUtil.isPatch(updates[j].replacement)) {
foundPatch = true;
key = updates[j].replacement.getId();
} else {
foundUpdate = true;
key = updates[j].toUpdate.getId();
}
Update latestUpdate = latestVersions.get(key);
IInstallableUnit latestIU = latestUpdate == null ? null : latestUpdate.replacement;
if (latestIU == null || updates[j].replacement.getVersion().compareTo(latestIU.getVersion()) > 0)
latestVersions.put(key, updates[j]);
}
// If there is a true update available, ignore any patches found
// Patches are keyed by their own id
if (foundPatch && foundUpdate) {
Set<String> keys = new HashSet<String>();
keys.addAll(latestVersions.keySet());
for (String id : keys) {
// Get rid of things keyed by a different id. We've already made sure
// that updates with a different id are keyed under the original id
if (!id.equals(iuToUpdate.getId())) {
latestVersions.remove(id);
}
}
}
elementsToPlan.addAll(latestVersions.values());
}
sub.worked(100);
}
if (toBeUpdated.size() <= 0 || elementsToPlan.isEmpty()) {
sub.done();
status.add(PlanAnalyzer.getStatus(IStatusCodes.NOTHING_TO_UPDATE, null));
return;
}
request = ProfileChangeRequest.createByProfileId(session.getProvisioningAgent(), profileId);
for (Update update : elementsToPlan) {
IInstallableUnit theUpdate = update.replacement;
if (defaultUpdates == null) {
defaultUpdates = new ArrayList<Update>();
defaultUpdates.add(update);
} else {
if (!defaultUpdates.contains(update))
defaultUpdates.add(update);
}
request.add(theUpdate);
request.setInstallableUnitProfileProperty(theUpdate, IProfile.PROP_PROFILE_ROOT_IU, Boolean.toString(true));
if (QueryUtil.isPatch(theUpdate)) {
request.setInstallableUnitInclusionRules(theUpdate, ProfileInclusionRules.createOptionalInclusionRule(theUpdate));
} else {
request.remove(update.toUpdate);
}
}
sub.done();
}
/* (non-Javadoc)
* @see org.eclipse.equinox.p2.operations.ProfileChangeOperation#getProvisioningJobName()
*/
protected String getProvisioningJobName() {
return Messages.UpdateOperation_UpdateJobName;
}
/* (non-Javadoc)
* @see org.eclipse.equinox.p2.operations.ProfileChangeOperation#getResolveJobName()
*/
protected String getResolveJobName() {
return Messages.UpdateOperation_ResolveJobName;
}
/*
* (non-Javadoc)
* @see org.eclipse.equinox.p2.operations.ProfileChangeOperation#prepareToResolve()
*/
protected void prepareToResolve() {
super.prepareToResolve();
if (iusToUpdate == null) {
iusToUpdate = getInstalledIUs();
}
}
/*
* Get the IInstallable units for the specified profile
*
* @param profileId the profile in question
* @param all <code>true</code> if all IInstallableUnits in the profile should
* be returned, <code>false</code> only those IInstallableUnits marked as (user visible) roots
* should be returned.
*
* @return an array of IInstallableUnits installed in the profile.
*/
private Collection<IInstallableUnit> getInstalledIUs() {
IProfile profile = session.getProfileRegistry().getProfile(profileId);
if (profile == null)
return CollectionUtils.emptyList();
IQuery<IInstallableUnit> query = new UserVisibleRootQuery();
IQueryResult<IInstallableUnit> queryResult = profile.query(query, null);
return queryResult.toUnmodifiableSet();
}
}