Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to 'bundles/org.eclipse.equinox.p2.operations/src/org/eclipse')
-rw-r--r--bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/internal/p2/operations/Activator.java34
-rw-r--r--bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/internal/p2/operations/DownloadPhaseSet.java25
-rw-r--r--bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/internal/p2/operations/IStatusCodes.java51
-rw-r--r--bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/internal/p2/operations/Messages.java76
-rw-r--r--bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/internal/p2/operations/PlanAnalyzer.java154
-rw-r--r--bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/internal/p2/operations/PlannerResolutionJob.java84
-rw-r--r--bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/internal/p2/operations/ResolutionResult.java125
-rw-r--r--bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/internal/p2/operations/SizingPhaseSet.java32
-rw-r--r--bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/internal/p2/operations/messages.properties44
-rw-r--r--bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/p2/operations/IProfileChangeJob.java29
-rw-r--r--bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/p2/operations/InstallOperation.java145
-rw-r--r--bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/p2/operations/ProfileChangeOperation.java353
-rw-r--r--bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/p2/operations/ProfileModificationJob.java114
-rw-r--r--bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/p2/operations/ProvisioningJob.java227
-rw-r--r--bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/p2/operations/ProvisioningSession.java317
-rw-r--r--bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/p2/operations/RepositoryTracker.java275
-rw-r--r--bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/p2/operations/UninstallOperation.java85
-rw-r--r--bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/p2/operations/Update.java72
-rw-r--r--bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/p2/operations/UpdateOperation.java277
-rw-r--r--bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/p2/operations/package.html33
20 files changed, 2552 insertions, 0 deletions
diff --git a/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/internal/p2/operations/Activator.java b/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/internal/p2/operations/Activator.java
new file mode 100644
index 000000000..9580bc9f7
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/internal/p2/operations/Activator.java
@@ -0,0 +1,34 @@
+/*******************************************************************************
+ * 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
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.operations;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+
+/**
+ * Activator class that registers the update checker service.
+ */
+public class Activator implements BundleActivator {
+ public static final String ID = "org.eclipse.equinox.p2.operations"; //$NON-NLS-1$
+ private static BundleContext context;
+
+ public static BundleContext getContext() {
+ return context;
+ }
+
+ public void start(BundleContext bundleContext) throws Exception {
+ Activator.context = bundleContext;
+ }
+
+ public void stop(BundleContext bundleContext) throws Exception {
+ Activator.context = null;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/internal/p2/operations/DownloadPhaseSet.java b/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/internal/p2/operations/DownloadPhaseSet.java
new file mode 100644
index 000000000..bd0f4a5d1
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/internal/p2/operations/DownloadPhaseSet.java
@@ -0,0 +1,25 @@
+/*******************************************************************************
+ * 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
+ *******************************************************************************/
+/**
+ *
+ */
+package org.eclipse.equinox.internal.p2.operations;
+
+import org.eclipse.equinox.internal.p2.engine.Phase;
+import org.eclipse.equinox.internal.p2.engine.PhaseSet;
+import org.eclipse.equinox.internal.p2.engine.phases.Collect;
+
+
+public class DownloadPhaseSet extends PhaseSet {
+ public DownloadPhaseSet() {
+ super(new Phase[] {new Collect(10)});
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/internal/p2/operations/IStatusCodes.java b/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/internal/p2/operations/IStatusCodes.java
new file mode 100644
index 000000000..34f92df10
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/internal/p2/operations/IStatusCodes.java
@@ -0,0 +1,51 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2009 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
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.operations;
+
+/**
+ * IStatusCodes defines codes for common status conditions when
+ * performing provisioning operations.
+ *
+ * This interface is not intended to be implemented
+ *
+ * @since 2.0
+ * @noimplement This interface is not intended to be implemented by clients.
+ */
+public interface IStatusCodes {
+
+ //Operation status codes [10000-10999] - note these cannot conflict with the core codes
+ //in ProvisionException or we'll see strange results.
+
+ public static final int NOTHING_TO_UPDATE = 10000;
+
+ // Status codes associated with profile change request or plans being altered from the original intent
+ public static final int PROFILE_CHANGE_ALTERED = 10001;
+ public static final int IU_REQUEST_ALTERED = 10002;
+ public static final int ALTERED_IMPLIED_UPDATE = 10003;
+ public static final int ALTERED_IGNORED_IMPLIED_DOWNGRADE = 10004;
+ public static final int ALTERED_IGNORED_ALREADY_INSTALLED = 10005;
+ public static final int ALTERED_PARTIAL_INSTALL = 10006;
+ public static final int ALTERED_PARTIAL_UNINSTALL = 10007;
+ public static final int ALTERED_SIDE_EFFECT_UPDATE = 10008;
+ public static final int ALTERED_SIDE_EFFECT_REMOVE = 10009;
+ public static final int ALTERED_SIDE_EFFECT_INSTALL = 10010;
+ public static final int ALTERED_IGNORED_INSTALL_REQUEST = 10011;
+ public static final int ALTERED_IGNORED_UNINSTALL_REQUEST = 10012;
+ public static final int ALTERED_IGNORED_IMPLIED_UPDATE = 10013;
+
+ // Status codes associated with inability to perform an operation
+ public static final int UNEXPECTED_NOTHING_TO_DO = 10050;
+ public static final int EXPECTED_NOTHING_TO_DO = 10051;
+ public static final int OPERATION_ALREADY_IN_PROGRESS = 10052;
+
+ // Status codes associated with repositories
+ public static final int INVALID_REPOSITORY_LOCATION = 10100;
+}
diff --git a/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/internal/p2/operations/Messages.java b/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/internal/p2/operations/Messages.java
new file mode 100644
index 000000000..55e98c177
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/internal/p2/operations/Messages.java
@@ -0,0 +1,76 @@
+/*******************************************************************************
+ * Copyright (c) 2009 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
+ ******************************************************************************/
+
+package org.eclipse.equinox.internal.p2.operations;
+
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * @since 2.0
+ *
+ */
+public class Messages extends NLS {
+ private static final String BUNDLE_NAME = "org.eclipse.equinox.internal.p2.operations.messages"; //$NON-NLS-1$
+
+ public static String InstallOperation_ComputeProfileChangeProgress;
+
+ public static String InstallOperation_InstallJobName;
+ public static String InstallOperation_ResolveJobName;
+
+ public static String ProfileChangeOperation_NoProfileChangeRequest;
+ public static String ProfileChangeOperation_ResolveTaskName;
+
+ public static String ProvisioningJob_GenericErrorStatusMessage;
+ public static String ProvisioningSession_AgentNotFound;
+
+ public static String ProvisioningSession_InstallPlanConfigurationError;
+
+ public static String PlanAnalyzer_IgnoringInstall;
+ public static String PlanAnalyzer_LockedImpliedUpdate0;
+ public static String PlanAnalyzer_PartialInstall;
+ public static String PlanAnalyzer_PartialUninstall;
+ public static String PlanAnalyzer_SideEffectInstall;
+ public static String PlanAnalyzer_SideEffectUninstall;
+ public static String PlannerResolutionJob_NullProvisioningPlan;
+
+ public static String PlanAnalyzer_IgnoringImpliedDowngrade;
+ public static String PlanAnalyzer_ImpliedUpdate;
+ public static String PlanAnalyzer_Items;
+ public static String PlanAnalyzer_NothingToDo;
+
+ public static String PlanAnalyzer_NoUpdates;
+ public static String PlanAnalyzer_AlreadyInstalled;
+ public static String PlanAnalyzer_AnotherOperationInProgress;
+ public static String PlanAnalyzer_RequestAltered;
+ public static String PlanAnalyzer_UnexpectedError;
+
+ public static String RepositoryTracker_DuplicateLocation;
+ public static String RepositoryTracker_InvalidLocation;
+
+ public static String ResolutionResult_SummaryStatus;
+
+ public static String SizingPhaseSet_PhaseSetName;
+
+ public static String UninstallOperation_ProvisioningJobName;
+ public static String UninstallOperation_ResolveJobName;
+
+ public static String UpdateOperation_ProfileChangeRequestProgress;
+ public static String UpdateOperation_ResolveJobName;
+ public static String UpdateOperation_UpdateJobName;
+
+ static {
+ // initialize resource bundle
+ NLS.initializeMessages(BUNDLE_NAME, Messages.class);
+ }
+
+ private Messages() {
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/internal/p2/operations/PlanAnalyzer.java b/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/internal/p2/operations/PlanAnalyzer.java
new file mode 100644
index 000000000..6a0a709f2
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/internal/p2/operations/PlanAnalyzer.java
@@ -0,0 +1,154 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2009 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
+ ******************************************************************************/
+
+package org.eclipse.equinox.internal.p2.operations;
+
+import java.util.Map.Entry;
+import org.eclipse.core.runtime.*;
+import org.eclipse.equinox.internal.provisional.p2.director.ProfileChangeRequest;
+import org.eclipse.equinox.internal.provisional.p2.director.RequestStatus;
+import org.eclipse.equinox.p2.engine.IProvisioningPlan;
+import org.eclipse.equinox.p2.metadata.IInstallableUnit;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * This class analyzes a profile change request and the resultant provisioning plan,
+ * and reports problems in a way that can be communicated to a user.
+ *
+ * @since 3.5
+ */
+public class PlanAnalyzer {
+
+ public static IStatus getStatus(int statusCode, IInstallableUnit affectedIU) {
+ switch (statusCode) {
+ case IStatusCodes.NOTHING_TO_UPDATE :
+ return new Status(IStatus.INFO, Activator.ID, statusCode, Messages.PlanAnalyzer_NoUpdates, null);
+ case IStatusCodes.PROFILE_CHANGE_ALTERED :
+ return new MultiStatus(Activator.ID, statusCode, Messages.PlanAnalyzer_RequestAltered, null);
+ case IStatusCodes.ALTERED_IMPLIED_UPDATE :
+ return new Status(IStatus.INFO, Activator.ID, statusCode, NLS.bind(Messages.PlanAnalyzer_ImpliedUpdate, getIUString(affectedIU)), null);
+ case IStatusCodes.ALTERED_IGNORED_IMPLIED_UPDATE :
+ return new Status(IStatus.WARNING, Activator.ID, statusCode, NLS.bind(Messages.PlanAnalyzer_LockedImpliedUpdate0, getIUString(affectedIU)), null);
+ case IStatusCodes.ALTERED_IGNORED_IMPLIED_DOWNGRADE :
+ return new Status(IStatus.WARNING, Activator.ID, statusCode, NLS.bind(Messages.PlanAnalyzer_IgnoringImpliedDowngrade, getIUString(affectedIU)), null);
+ case IStatusCodes.ALTERED_IGNORED_ALREADY_INSTALLED :
+ return new Status(IStatus.WARNING, Activator.ID, statusCode, NLS.bind(Messages.PlanAnalyzer_AlreadyInstalled, getIUString(affectedIU)), null);
+ case IStatusCodes.ALTERED_PARTIAL_INSTALL :
+ return new Status(IStatus.INFO, Activator.ID, statusCode, NLS.bind(Messages.PlanAnalyzer_PartialInstall, getIUString(affectedIU)), null);
+ case IStatusCodes.ALTERED_PARTIAL_UNINSTALL :
+ return new Status(IStatus.INFO, Activator.ID, statusCode, NLS.bind(Messages.PlanAnalyzer_PartialUninstall, getIUString(affectedIU)), null);
+ case IStatusCodes.UNEXPECTED_NOTHING_TO_DO :
+ return new Status(IStatus.ERROR, Activator.ID, statusCode, NLS.bind(Messages.PlanAnalyzer_NothingToDo, getIUString(affectedIU)), null);
+ case IStatusCodes.OPERATION_ALREADY_IN_PROGRESS :
+ return new Status(IStatus.ERROR, Activator.ID, statusCode, Messages.PlanAnalyzer_AnotherOperationInProgress, null);
+ default :
+ return new Status(IStatus.ERROR, Activator.ID, statusCode, NLS.bind(Messages.PlanAnalyzer_UnexpectedError, new Integer(statusCode), getIUString(affectedIU)), null);
+ }
+ }
+
+ public static MultiStatus getProfileChangeAlteredStatus() {
+ return (MultiStatus) getStatus(IStatusCodes.PROFILE_CHANGE_ALTERED, null);
+ }
+
+ public static ResolutionResult computeResolutionResult(ProfileChangeRequest originalRequest, IProvisioningPlan plan, MultiStatus originalStatus) {
+ Assert.isNotNull(originalRequest);
+ Assert.isNotNull(plan);
+ Assert.isNotNull(originalStatus);
+
+ ResolutionResult report = new ResolutionResult();
+
+ // If the plan was canceled, no further analysis is needed
+ if (plan.getStatus().getSeverity() == IStatus.CANCEL) {
+ report.addSummaryStatus(plan.getStatus());
+ return report;
+ }
+
+ if (nothingToDo(originalRequest)) {
+ report.addSummaryStatus(getStatus(IStatusCodes.UNEXPECTED_NOTHING_TO_DO, null));
+ IStatus[] details = originalStatus.getChildren();
+ for (int i = 0; i < details.length; i++)
+ report.addSummaryStatus(details[i]);
+ return report;
+ }
+
+ // If there was already some status supplied before resolution, this should get included
+ // with the report. For example, this might contain information about the profile request
+ // being altered before resolution began.
+ if (originalStatus != null && originalStatus.getChildren().length > 0) {
+ report.addSummaryStatus(originalStatus);
+ }
+
+ // If the overall plan had a non-OK status, capture that in the report.
+ if (!plan.getStatus().isOK())
+ report.addSummaryStatus(plan.getStatus());
+
+ // Now we compare what was requested with what is going to happen.
+ // In the long run, when a RequestStatus can provide actual explanation/status
+ // about failures, we might want to add this information to the overall status.
+ // As it stands now, if the provisioning plan is in error, that info is more detailed
+ // than the request status. So we will only add request status info to the overall
+ // status when the overall status is not in error.
+ if (plan.getStatus().getSeverity() != IStatus.ERROR) {
+ IInstallableUnit[] iusAdded = originalRequest.getAddedInstallableUnits();
+ for (int i = 0; i < iusAdded.length; i++) {
+ RequestStatus rs = (RequestStatus) plan.getRequestStatus(iusAdded[i]);
+ if (rs.getSeverity() == IStatus.ERROR) {
+ // This is a serious error so it must also appear in the overall status
+ IStatus fail = new Status(IStatus.ERROR, Activator.ID, IStatusCodes.ALTERED_IGNORED_INSTALL_REQUEST, NLS.bind(Messages.PlanAnalyzer_IgnoringInstall, getIUString(iusAdded[i])), null);
+ report.addStatus(iusAdded[i], fail);
+ report.addSummaryStatus(fail);
+ }
+ }
+ IInstallableUnit[] iusRemoved = originalRequest.getRemovedInstallableUnits();
+ for (int i = 0; i < iusRemoved.length; i++) {
+ RequestStatus rs = (RequestStatus) plan.getRequestStatus(iusRemoved[i]);
+ if (rs.getSeverity() == IStatus.ERROR) {
+ // TODO see https://bugs.eclipse.org/bugs/show_bug.cgi?id=255984
+ // We are making assumptions here about why the planner chose to ignore an uninstall.
+ // Assume it could not be uninstalled because of some other dependency, yet the planner did not view
+ // this as an error. So we inform the user that we can only uninstall parts of it. The root property will be
+ // removed per the original change request.
+ IStatus fail = new Status(IStatus.INFO, Activator.ID, IStatusCodes.ALTERED_PARTIAL_UNINSTALL, NLS.bind(Messages.PlanAnalyzer_PartialUninstall, getIUString(iusRemoved[i])), null);
+ report.addStatus(iusRemoved[i], fail);
+ report.addSummaryStatus(fail);
+ }
+ }
+ }
+
+ // Now process the side effects
+ for (Entry<IInstallableUnit, IStatus> entry : plan.getSideEffectChanges().entrySet()) {
+ IInstallableUnit iu = entry.getKey();
+ RequestStatus rs = (RequestStatus) entry.getValue();
+ if (rs.getInitialRequestType() == RequestStatus.ADDED) {
+ report.addStatus(iu, new Status(rs.getSeverity(), Activator.ID, IStatusCodes.ALTERED_SIDE_EFFECT_INSTALL, NLS.bind(Messages.PlanAnalyzer_SideEffectInstall, getIUString(iu)), null));
+ } else {
+ report.addStatus(iu, new Status(rs.getSeverity(), Activator.ID, IStatusCodes.ALTERED_SIDE_EFFECT_REMOVE, NLS.bind(Messages.PlanAnalyzer_SideEffectUninstall, getIUString(iu)), null));
+ }
+ }
+
+ return report;
+
+ }
+
+ private static String getIUString(IInstallableUnit iu) {
+ if (iu == null)
+ return Messages.PlanAnalyzer_Items;
+ // Get the iu name in the default locale
+ String name = iu.getProperty(IInstallableUnit.PROP_NAME, null);
+ if (name != null)
+ return name;
+ return iu.getId();
+ }
+
+ private static boolean nothingToDo(ProfileChangeRequest request) {
+ return request.getAddedInstallableUnits().length == 0 && request.getRemovedInstallableUnits().length == 0 && request.getInstallableUnitProfilePropertiesToAdd().size() == 0 && request.getInstallableUnitProfilePropertiesToRemove().size() == 0;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/internal/p2/operations/PlannerResolutionJob.java b/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/internal/p2/operations/PlannerResolutionJob.java
new file mode 100644
index 000000000..14ed2c330
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/internal/p2/operations/PlannerResolutionJob.java
@@ -0,0 +1,84 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2009 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
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.operations;
+
+import org.eclipse.core.runtime.*;
+import org.eclipse.equinox.internal.provisional.p2.director.ProfileChangeRequest;
+import org.eclipse.equinox.p2.engine.IProvisioningPlan;
+import org.eclipse.equinox.p2.engine.ProvisioningContext;
+import org.eclipse.equinox.p2.operations.*;
+
+/**
+ * Class representing a provisioning profile plan
+ *
+ * @since 2.0
+ */
+public class PlannerResolutionJob extends ProvisioningJob implements IProfileChangeJob {
+
+ ProfileChangeRequest request;
+ String profileId;
+ IProvisioningPlan plan;
+ MultiStatus additionalStatus;
+ ResolutionResult report;
+ ProvisioningContext provisioningContext;
+
+ public static MultiStatus getProfileChangeRequestAlteredStatus() {
+ return PlanAnalyzer.getProfileChangeAlteredStatus();
+ }
+
+ public PlannerResolutionJob(String label, ProvisioningSession session, String profileId, ProfileChangeRequest request, ProvisioningContext provisioningContext, MultiStatus additionalStatus) {
+ super(label, session);
+ this.request = request;
+ this.profileId = profileId;
+ if (provisioningContext == null)
+ this.provisioningContext = new ProvisioningContext();
+ else
+ this.provisioningContext = provisioningContext;
+ Assert.isNotNull(additionalStatus);
+ this.additionalStatus = additionalStatus;
+ }
+
+ public IProvisioningPlan getProvisioningPlan() {
+ return plan;
+ }
+
+ public ProfileChangeRequest getProfileChangeRequest() {
+ return request;
+ }
+
+ public ProvisioningContext getProvisioningContext() {
+ return provisioningContext;
+ }
+
+ public void setProvisioningContext(ProvisioningContext context) {
+ this.provisioningContext = context;
+ }
+
+ public IStatus runModal(IProgressMonitor monitor) {
+ plan = getSession().getPlanner().getProvisioningPlan(request, provisioningContext, monitor);
+ if (plan == null) {
+ return new Status(IStatus.ERROR, Activator.ID, Messages.PlannerResolutionJob_NullProvisioningPlan);
+ }
+ return plan.getStatus();
+
+ }
+
+ public ResolutionResult getResolutionResult() {
+ if (report == null) {
+ report = PlanAnalyzer.computeResolutionResult(request, plan, additionalStatus);
+ }
+ return report;
+ }
+
+ public String getProfileId() {
+ return profileId;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/internal/p2/operations/ResolutionResult.java b/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/internal/p2/operations/ResolutionResult.java
new file mode 100644
index 000000000..0d0d90bab
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/internal/p2/operations/ResolutionResult.java
@@ -0,0 +1,125 @@
+/*******************************************************************************
+ * Copyright (c) 2009 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
+ ******************************************************************************/
+
+package org.eclipse.equinox.internal.p2.operations;
+
+import java.util.HashMap;
+import org.eclipse.core.runtime.*;
+import org.eclipse.equinox.p2.metadata.IInstallableUnit;
+
+/**
+ * ResolutionResult describes problems in a provisioning plan in a structured
+ * way that can be presented to a user.
+ *
+ * @since 2.0
+ */
+public class ResolutionResult {
+ private static final String NESTING_INDENT = " "; //$NON-NLS-1$
+
+ private final HashMap<IInstallableUnit, MultiStatus> iuToStatusMap = new HashMap<IInstallableUnit, MultiStatus>();
+ private MultiStatus summaryStatus;
+
+ public IStatus getSummaryStatus() {
+ if (summaryStatus != null)
+ return summaryStatus;
+ return Status.OK_STATUS;
+ }
+
+ public void addSummaryStatus(IStatus status) {
+ if (summaryStatus == null) {
+ summaryStatus = new MultiStatus(Activator.ID, 0, Messages.ResolutionResult_SummaryStatus, null);
+ }
+ summaryStatus.add(status);
+ }
+
+ public IStatus statusOf(IInstallableUnit iu) {
+ return iuToStatusMap.get(iu);
+ }
+
+ public void addStatus(IInstallableUnit iu, IStatus status) {
+ MultiStatus iuSummaryStatus = iuToStatusMap.get(iu);
+ if (iuSummaryStatus == null) {
+ iuSummaryStatus = new MultiStatus(Activator.ID, IStatusCodes.IU_REQUEST_ALTERED, new IStatus[] {status}, getIUString(iu), null);
+ } else
+ iuSummaryStatus.add(status);
+ }
+
+ private String getIUString(IInstallableUnit iu) {
+ if (iu == null)
+ return Messages.PlanAnalyzer_Items;
+ // Get the iu name in the default locale
+ String name = iu.getProperty(IInstallableUnit.PROP_NAME, null);
+ if (name != null)
+ return name;
+ return iu.getId();
+ }
+
+ public String getSummaryReport() {
+ if (summaryStatus != null) {
+ StringBuffer buffer = new StringBuffer();
+ appendDetailText(summaryStatus, buffer, -1, false);
+ return buffer.toString();
+ }
+ return ""; //$NON-NLS-1$
+ }
+
+ // Answers null if there is nothing to say about the ius
+ public String getDetailedReport(IInstallableUnit[] ius) {
+ StringBuffer buffer = new StringBuffer();
+ for (int i = 0; i < ius.length; i++) {
+ MultiStatus iuStatus = iuToStatusMap.get(ius[i]);
+ if (iuStatus != null)
+ appendDetailText(iuStatus, buffer, 0, true);
+ }
+ String report = buffer.toString();
+ if (report.length() == 0)
+ return null;
+ return report;
+ }
+
+ void appendDetailText(IStatus status, StringBuffer buffer, int indent, boolean includeTopLevelMessage) {
+ if (includeTopLevelMessage) {
+ for (int i = 0; i < indent; i++)
+ buffer.append(NESTING_INDENT);
+ if (status.getMessage() != null)
+ buffer.append(status.getMessage());
+ }
+ Throwable t = status.getException();
+ if (t != null) {
+ // A provision (or core) exception occurred. Get its status message or if none, its top level message.
+ // Indent by one more level (note the <=)
+ buffer.append('\n');
+ for (int i = 0; i <= indent; i++)
+ buffer.append(NESTING_INDENT);
+ if (t instanceof CoreException) {
+ IStatus exceptionStatus = ((CoreException) t).getStatus();
+ if (exceptionStatus != null && exceptionStatus.getMessage() != null)
+ buffer.append(exceptionStatus.getMessage());
+ else {
+ String details = t.getLocalizedMessage();
+ if (details != null)
+ buffer.append(details);
+ }
+ } else {
+ String details = t.getLocalizedMessage();
+ if (details != null)
+ buffer.append(details);
+ }
+ }
+ // Now print the children status info (if there are children)
+ IStatus[] children = status.getChildren();
+ for (int i = 0; i < children.length; i++) {
+ if (buffer.length() > 0)
+ buffer.append('\n');
+ appendDetailText(children[i], buffer, indent + 1, true);
+ }
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/internal/p2/operations/SizingPhaseSet.java b/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/internal/p2/operations/SizingPhaseSet.java
new file mode 100644
index 000000000..1b433695b
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/internal/p2/operations/SizingPhaseSet.java
@@ -0,0 +1,32 @@
+/*******************************************************************************
+ * 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
+ *******************************************************************************/
+/**
+ *
+ */
+package org.eclipse.equinox.internal.p2.operations;
+
+import org.eclipse.equinox.internal.p2.engine.Phase;
+import org.eclipse.equinox.internal.p2.engine.PhaseSet;
+import org.eclipse.equinox.internal.p2.engine.phases.Sizing;
+
+
+public class SizingPhaseSet extends PhaseSet {
+
+ private static Sizing sizing;
+
+ public SizingPhaseSet() {
+ super(new Phase[] {sizing = new Sizing(100, Messages.SizingPhaseSet_PhaseSetName)});
+ }
+
+ public Sizing getSizing() {
+ return sizing;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/internal/p2/operations/messages.properties b/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/internal/p2/operations/messages.properties
new file mode 100644
index 000000000..24c99f571
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/internal/p2/operations/messages.properties
@@ -0,0 +1,44 @@
+###############################################################################
+# Copyright (c) 2009 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
+###############################################################################
+InstallOperation_ComputeProfileChangeProgress=Checking the install request
+InstallOperation_InstallJobName=Installing Software
+InstallOperation_ResolveJobName=Computing install requirements
+ProfileChangeOperation_NoProfileChangeRequest=Could not interpret the installation request.
+ProfileChangeOperation_ResolveTaskName=Analyzing the install operation
+ProvisioningJob_GenericErrorStatusMessage=Error encountered while running {0}
+ProvisioningSession_AgentNotFound=No provisioning agent could be found
+ProvisioningSession_InstallPlanConfigurationError=A problem was encountered while preparing the system for the installation
+PlanAnalyzer_IgnoringInstall="{0}" is not applicable to the current configuration and will not be installed.
+PlanAnalyzer_LockedImpliedUpdate0={0} will be ignored because it is already installed, and updates are not permitted.
+PlanAnalyzer_PartialInstall="{0}" is already present because other installed software requires it. It will be added to the installed software list.
+PlanAnalyzer_PartialUninstall= "{0}" cannot be fully uninstalled because other installed software requires it. The parts that are not required will be uninstalled.
+PlanAnalyzer_SideEffectInstall="{0}" will also be installed in order to complete this operation.
+PlanAnalyzer_SideEffectUninstall="{0}" must be uninstalled in order to complete this operation.
+PlannerResolutionJob_NullProvisioningPlan=Could not obtain provisioning plan. No details were available.
+PlanAnalyzer_IgnoringImpliedDowngrade="{0}" will be ignored because a newer version is already installed.
+PlanAnalyzer_ImpliedUpdate="{0}" is already installed, so an update will be performed instead.
+PlanAnalyzer_Items=Items
+PlanAnalyzer_NothingToDo=Cannot complete the request. See the error log for details.
+PlanAnalyzer_NoUpdates=No updates were found.
+PlanAnalyzer_AlreadyInstalled="{0}" will be ignored because it is already installed.
+PlanAnalyzer_AnotherOperationInProgress=Cannot continue the operation. There is another install operation in progress.
+PlanAnalyzer_RequestAltered=Your original request has been modified.
+PlanAnalyzer_UnexpectedError=Unexpected error code {0} encountered for {1}.
+RepositoryTracker_DuplicateLocation=Duplicate location
+RepositoryTracker_InvalidLocation=The location "{0}" is not a valid software site location.
+ResolutionResult_SummaryStatus=Operation details
+SizingPhaseSet_PhaseSetName=Compute sizes
+
+UninstallOperation_ProvisioningJobName=Uninstalling Software
+UninstallOperation_ResolveJobName=Calculating Uninstall Validity
+UpdateOperation_ProfileChangeRequestProgress=Computing update request
+UpdateOperation_ResolveJobName=Computing Update Requirements
+UpdateOperation_UpdateJobName=Updating Software
diff --git a/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/p2/operations/IProfileChangeJob.java b/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/p2/operations/IProfileChangeJob.java
new file mode 100644
index 000000000..c415ed5b6
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/p2/operations/IProfileChangeJob.java
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * Copyright (c) 2009 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
+ ******************************************************************************/
+
+package org.eclipse.equinox.p2.operations;
+
+/**
+ * Interface for a provisioning job that operates on a
+ * profile.
+ *
+ * @since 2.0
+ */
+public interface IProfileChangeJob {
+
+ /**
+ * Return the string id of the profile involved in this job.
+ *
+ * @return the id of the profile
+ */
+ public String getProfileId();
+
+}
diff --git a/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/p2/operations/InstallOperation.java b/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/p2/operations/InstallOperation.java
new file mode 100644
index 000000000..b20f96edb
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/p2/operations/InstallOperation.java
@@ -0,0 +1,145 @@
+/*******************************************************************************
+ * Copyright (c) 2009 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
+ ******************************************************************************/
+
+package org.eclipse.equinox.p2.operations;
+
+import org.eclipse.core.runtime.*;
+import org.eclipse.equinox.internal.p2.operations.*;
+import org.eclipse.equinox.internal.provisional.p2.director.PlannerHelper;
+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.metadata.query.InstallableUnitQuery;
+import org.eclipse.equinox.p2.metadata.query.PatchQuery;
+import org.eclipse.equinox.p2.query.IQueryResult;
+
+/**
+ * An InstallOperation describes an operation that installs IInstallableUnits into
+ * a profile.
+ *
+ * The following snippet shows how one might use an InstallOperation to perform a synchronous resolution and
+ * then kick off an install in the background:
+ *
+ * <pre>
+ * InstallOperation op = new InstallOperation(session, new IInstallableUnit [] { myIU });
+ * IStatus result = op.resolveModal(monitor);
+ * if (result.isOK()) {
+ * op.getProvisioningJob(monitor).schedule();
+ * }
+ * </pre>
+ *
+ * @since 2.0
+ * @see ProfileChangeOperation
+ * @noextend This class is not intended to be subclassed by clients.
+ */
+public class InstallOperation extends ProfileChangeOperation {
+
+ private IInstallableUnit[] toInstall;
+
+ /**
+ * Create an install operation on the specified provisioning session that installs
+ * the supplied 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 toInstall the IInstallableUnits to be installed into the profile.
+ */
+ public InstallOperation(ProvisioningSession session, IInstallableUnit[] toInstall) {
+ super(session);
+ this.toInstall = toInstall;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.equinox.p2.operations.ProfileChangeOperation#computeProfileChangeRequest(org.eclipse.core.runtime.MultiStatus, org.eclipse.core.runtime.IProgressMonitor)
+ */
+ protected void computeProfileChangeRequest(MultiStatus status, IProgressMonitor monitor) {
+ request = ProfileChangeRequest.createByProfileId(profileId);
+ IProfile profile;
+ profile = session.getProfileRegistry().getProfile(profileId);
+ SubMonitor sub = SubMonitor.convert(monitor, Messages.InstallOperation_ComputeProfileChangeProgress, toInstall.length);
+ for (int i = 0; i < toInstall.length; i++) {
+ // If the user is installing a patch, we mark it optional. This allows
+ // the patched IU to be updated later by removing the patch.
+ if (PatchQuery.isPatch(toInstall[i]))
+ request.setInstallableUnitInclusionRules(toInstall[i], PlannerHelper.createOptionalInclusionRule(toInstall[i]));
+
+ // Check to see if it is already installed. This may alter the request.
+ IQueryResult<IInstallableUnit> alreadyInstalled = profile.query(new InstallableUnitQuery(toInstall[i].getId()), null);
+ // TODO ideally we should only do this check if the iu is a singleton, but in practice many iu's that should
+ // be singletons are not, so we don't check this (yet)
+ // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=230878
+ if (!alreadyInstalled.isEmpty()) { // && installedIU.isSingleton()
+ IInstallableUnit installedIU = alreadyInstalled.iterator().next();
+ int compareTo = toInstall[i].getVersion().compareTo(installedIU.getVersion());
+ // If the iu is a newer version of something already installed, consider this an
+ // update request
+ if (compareTo > 0) {
+ boolean lockedForUpdate = false;
+ String value = profile.getInstallableUnitProperty(installedIU, IProfile.PROP_PROFILE_LOCKED_IU);
+ if (value != null)
+ lockedForUpdate = (Integer.parseInt(value) & IProfile.LOCK_UPDATE) == IProfile.LOCK_UPDATE;
+ if (lockedForUpdate) {
+ // Add a status telling the user that this implies an update, but the
+ // iu should not be updated
+ status.merge(PlanAnalyzer.getStatus(IStatusCodes.ALTERED_IGNORED_IMPLIED_UPDATE, toInstall[i]));
+ } else {
+ request.addInstallableUnits(toInstall[i]);
+ request.removeInstallableUnit(installedIU);
+ // Add a status informing the user that the update has been inferred
+ status.merge(PlanAnalyzer.getStatus(IStatusCodes.ALTERED_IMPLIED_UPDATE, toInstall[i]));
+ // Mark it as a root if it hasn't been already
+ if (!UserVisibleRootQuery.isUserVisible(installedIU, profile))
+ request.setInstallableUnitProfileProperty(toInstall[i], IProfile.PROP_PROFILE_ROOT_IU, Boolean.toString(true));
+ }
+ } else if (compareTo < 0) {
+ // An implied downgrade. We will not put this in the plan, add a status informing the user
+ status.merge(PlanAnalyzer.getStatus(IStatusCodes.ALTERED_IGNORED_IMPLIED_DOWNGRADE, toInstall[i]));
+ } else {
+ // if (rootMarkerKey != null) {
+ if (UserVisibleRootQuery.isUserVisible(installedIU, profile))
+ // It is already a root, nothing to do. We tell the user it was already installed
+ status.merge(PlanAnalyzer.getStatus(IStatusCodes.ALTERED_IGNORED_ALREADY_INSTALLED, toInstall[i]));
+ else {
+ // It was already installed but not as a root. Tell the user that parts of it are already installed and mark
+ // it as a root.
+ status.merge(PlanAnalyzer.getStatus(IStatusCodes.ALTERED_PARTIAL_INSTALL, toInstall[i]));
+ request.setInstallableUnitProfileProperty(toInstall[i], IProfile.PROP_PROFILE_ROOT_IU, Boolean.toString(true));
+ }
+ // }
+ }
+ } else {
+ // Install it and mark as a root
+ request.addInstallableUnits(new IInstallableUnit[] {toInstall[i]});
+ // if (rootMarkerKey != null)
+ request.setInstallableUnitProfileProperty(toInstall[i], IProfile.PROP_PROFILE_ROOT_IU, Boolean.toString(true));
+ }
+ sub.worked(1);
+ }
+ sub.done();
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.equinox.p2.operations.ProfileChangeOperation#getResolveJobName()
+ */
+ protected String getResolveJobName() {
+ return Messages.InstallOperation_ResolveJobName;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.equinox.p2.operations.ProfileChangeOperation#getProvisioningJobName()
+ */
+ protected String getProvisioningJobName() {
+ return Messages.InstallOperation_InstallJobName;
+
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/p2/operations/ProfileChangeOperation.java b/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/p2/operations/ProfileChangeOperation.java
new file mode 100644
index 000000000..7667e88ec
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/p2/operations/ProfileChangeOperation.java
@@ -0,0 +1,353 @@
+/*******************************************************************************
+ * Copyright (c) 2009 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
+ ******************************************************************************/
+
+package org.eclipse.equinox.p2.operations;
+
+import org.eclipse.core.runtime.*;
+import org.eclipse.equinox.internal.p2.operations.*;
+import org.eclipse.equinox.internal.p2.operations.Messages;
+import org.eclipse.equinox.internal.provisional.p2.director.ProfileChangeRequest;
+import org.eclipse.equinox.p2.engine.*;
+import org.eclipse.equinox.p2.metadata.IInstallableUnit;
+
+/**
+ * ProfileChangeOperation describes a provisioning operation that modifies a profile.
+ * The operation must first be resolved, followed by the actual provisioning
+ * work being performed. This two-pass nature of the ProfileChangeOperation allows
+ * resolution status to be reported to a client to determine whether the operation
+ * should proceed. Each phase of the operation can be performed synchronously or in
+ * the background as a job. To perform the operation synchronously:
+ *
+ * <pre>
+ * IStatus result = op.resolveModal(monitor);
+ * if (result.isOK())
+ * op.getProvisioningJob(null).runModal(monitor);
+ * else {
+ * // interpret the result
+ * }
+ * </pre>
+ *
+ * To perform the resolution synchronously and the provisioning job in the
+ * background:
+ *
+ * <pre>
+ * IStatus status = op.resolveModal(monitor);
+ * if (status.isOK()) {
+ * ProvisioningJob job = op.getProvisioningJob(monitor);
+ * job.schedule();
+ * } else {
+ * // interpret the result
+ * }
+ * </pre>
+ *
+ * To resolve in the background and perform the job when it is complete:
+ *
+ * <pre>
+ * ProvisioningJob job = op.getResolveJob(monitor);
+ * job.addJobChangeListener(new JobChangeAdapter() {
+ * public void done (JobChangeEvent event) {
+ * if (event.getResult().isOK() {
+ * op.getProvisioningJob(monitor).schedule();
+ * } else {
+ * // interpret the result
+ * }
+ * }
+ * });
+ * job.schedule();
+ *
+ * </pre>
+ *
+ * In general, it is expected that clients create a new ProfileChangeOperation if
+ * the resolution result of the current operation is not satisfactory. However,
+ * subclasses may prescribe a different life cycle where appropriate.
+ *
+ * When retrieving the resolution and provisioning jobs managed by this operation,
+ * a client may supply a progress monitor to be used by the job. When the job is
+ * run by the platform job manager, both the platform job manager progress indicator
+ * and the monitor supplied by the client will be updated.
+ *
+ * @noextend This class is not intended to be subclassed by clients.
+ * @since 2.0
+ */
+public abstract class ProfileChangeOperation implements IProfileChangeJob {
+
+ ProvisioningSession session;
+ String profileId;
+ ProvisioningContext context;
+ MultiStatus noChangeRequest;
+ PlannerResolutionJob job;
+ ProfileChangeRequest request;
+
+ /**
+ * Create an operation using the provided provisioning session.
+ * Unless otherwise specified by the client, the operation is
+ * performed on the currently running profile.
+ *
+ * @param session the provisioning session providing the services
+ */
+ protected ProfileChangeOperation(ProvisioningSession session) {
+ this.session = session;
+ this.profileId = IProfileRegistry.SELF;
+ }
+
+ /**
+ * Resolve the operation in the current thread using the specified progress
+ * monitor. Return a status describing the result of the resolution.
+ *
+ * @param monitor the progress monitor to use
+ * @return a status describing the resolution results
+ */
+ public final IStatus resolveModal(IProgressMonitor monitor) {
+ if (monitor == null)
+ monitor = new NullProgressMonitor();
+ prepareToResolve();
+ makeResolveJob(monitor);
+ if (job != null) {
+ job.runModal(monitor);
+ }
+ return getResolutionResult();
+
+ }
+
+ /**
+ * Set the id of the profile that will be modified by this operation.
+ * @param id the profile id
+ */
+ public void setProfileId(String id) {
+ this.profileId = id;
+ }
+
+ /**
+ * Return a job that can be used to resolve this operation in the background.
+ *
+ * @param monitor a progress monitor that should be used to report the job's progress in addition
+ * to the standard job progress reporting. Can be <code>null</code>. If provided, this monitor
+ * will be called from a background thread.
+ *
+ * @return a job that can be scheduled to perform the provisioning operation.
+ */
+ public final ProvisioningJob getResolveJob(IProgressMonitor monitor) {
+ SubMonitor mon = SubMonitor.convert(monitor, Messages.ProfileChangeOperation_ResolveTaskName, 1000);
+ prepareToResolve();
+ makeResolveJob(mon.newChild(100));
+ job.setAdditionalProgressMonitor(mon.newChild(900));
+ return job;
+ }
+
+ /**
+ * Perform any processing that must occur just before resolving this operation.
+ */
+ protected void prepareToResolve() {
+ // default is to do nothing
+ }
+
+ private void makeResolveJob(IProgressMonitor monitor) {
+ noChangeRequest = PlanAnalyzer.getProfileChangeAlteredStatus();
+ if (session.hasScheduledOperationsFor(profileId)) {
+ noChangeRequest.add(PlanAnalyzer.getStatus(IStatusCodes.OPERATION_ALREADY_IN_PROGRESS, null));
+ } else {
+ computeProfileChangeRequest(noChangeRequest, monitor);
+ }
+ if (request == null) {
+ if (noChangeRequest.getChildren().length == 0)
+ // No explanation for failure was provided. It shouldn't happen, but...
+ noChangeRequest = new MultiStatus(Activator.ID, IStatusCodes.UNEXPECTED_NOTHING_TO_DO, new IStatus[] {PlanAnalyzer.getStatus(IStatusCodes.UNEXPECTED_NOTHING_TO_DO, null)}, Messages.ProfileChangeOperation_NoProfileChangeRequest, null);
+ return;
+ }
+ createPlannerResolutionJob();
+ }
+
+ /**
+ * Compute the profile change request for this operation, adding any relevant intermediate status
+ * to the supplied status.
+ *
+ * @param status a multi-status to be used to add relevant status. If a profile change request cannot
+ * be computed for any reason, a status should be added to explain the problem.
+ *
+ * @param monitor the progress monitor to use for computing the profile change request
+ */
+ protected abstract void computeProfileChangeRequest(MultiStatus status, IProgressMonitor monitor);
+
+ private void createPlannerResolutionJob() {
+ job = new PlannerResolutionJob(getResolveJobName(), session, profileId, request, context, noChangeRequest);
+ }
+
+ /**
+ * Return an appropriate name for the resolution job.
+ *
+ * @return the resolution job name.
+ */
+ protected abstract String getResolveJobName();
+
+ /**
+ * Return an appropriate name for the provisioning job.
+ *
+ * @return the provisioning job name.
+ */
+ protected abstract String getProvisioningJobName();
+
+ /**
+ * Return a status indicating the result of resolving this
+ * operation. A <code>null</code> return indicates that
+ * resolving has not occurred yet.
+ *
+ * @return the status of the resolution, or <code>null</code>
+ * if resolution has not yet occurred.
+ */
+ public IStatus getResolutionResult() {
+ if (job != null && job.getResolutionResult() != null)
+ return job.getResolutionResult().getSummaryStatus();
+ if (request == null && noChangeRequest != null) {
+ // If there is only one child message, use the specific message
+ if (noChangeRequest.getChildren().length == 1)
+ return noChangeRequest.getChildren()[0];
+ return noChangeRequest;
+ }
+ return null;
+ }
+
+ /**
+ * Return a string that can be used to describe the results of the resolution
+ * to a client.
+ *
+ * @return a string describing the resolution details, or <code>null</code> if the
+ * operation has not been resolved.
+ */
+ public String getResolutionDetails() {
+ if (job != null && job.getResolutionResult() != null)
+ return job.getResolutionResult().getSummaryReport();
+ return null;
+
+ }
+
+ /**
+ * Return a string that describes the specific resolution results
+ * related to the supplied {@link IInstallableUnit}.
+ *
+ * @param iu the IInstallableUnit for which resolution details are requested
+ *
+ * @return a string describing the results for the installable unit, or <code>null</code> if
+ * there are no specific results available for the installable unit.
+ */
+ public String getResolutionDetails(IInstallableUnit iu) {
+ if (job != null && job.getResolutionResult() != null)
+ return job.getResolutionResult().getDetailedReport(new IInstallableUnit[] {iu});
+ return null;
+
+ }
+
+ /**
+ * Return the provisioning plan obtained by resolving the receiver.
+ *
+ * @return the provisioning plan. This may be <code>null</code> if the operation
+ * has not been resolved, or if a plan could not be obtained when attempting to
+ * resolve. If the plan is null and the operation has been resolved, then the
+ * resolution result will explain the problem.
+ *
+ * @see #hasResolved()
+ * @see #getResolutionResult()
+ */
+ public IProvisioningPlan getProvisioningPlan() {
+ if (job != null)
+ return job.getProvisioningPlan();
+ return null;
+ }
+
+ /**
+ * Return the profile change request that describes the receiver.
+ *
+ * @return the profile change request. This may be <code>null</code> if the operation
+ * has not been resolved, or if a profile change request could not be assembled given
+ * the operation's state. If the profile change request is null and the operation has
+ * been resolved, the the resolution result will explain the problem.
+ *
+ * @see #hasResolved()
+ * @see #getResolutionResult()
+ */
+ public ProfileChangeRequest getProfileChangeRequest() {
+ if (job != null)
+ return job.getProfileChangeRequest();
+ return null;
+ }
+
+ /**
+ * Return a provisioning job that can be used to perform the resolved operation.
+ *
+ * @param monitor a progress monitor that should be used to report the job's progress in addition
+ * to the standard job progress reporting. Can be <code>null</code>. If provided, this monitor
+ * will be called from a background thread.
+ *
+ * @return a job that can be used to perform the provisioning operation. This may be <code>null</code>
+ * if the operation has not been resolved, or if a plan could not be obtained when attempting to
+ * resolve. If the job is null and the operation has been resolved, then the resolution result
+ * will explain the problem.
+ *
+ * @see #hasResolved()
+ * @see #getResolutionResult()
+ */
+ public ProvisioningJob getProvisioningJob(IProgressMonitor monitor) {
+ IStatus status = getResolutionResult();
+ if (status.getSeverity() != IStatus.CANCEL && status.getSeverity() != IStatus.ERROR) {
+ if (job.getProvisioningPlan() != null) {
+ ProfileModificationJob pJob = new ProfileModificationJob(getProvisioningJobName(), session, profileId, job.getProvisioningPlan(), context);
+ pJob.setAdditionalProgressMonitor(monitor);
+ return pJob;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Set the provisioning context that should be used to resolve and perform the provisioning for
+ * the operation. This must be set before an attempt is made to resolve the operation
+ * for it to have any effect.
+ *
+ * @param context the provisioning context.
+ */
+ public void setProvisioningContext(ProvisioningContext context) {
+ this.context = context;
+ if (job != null)
+ job.setProvisioningContext(context);
+ }
+
+ /**
+ * Get the provisioning context that will be used to resolve and perform the provisioning for
+ * the operation.
+ *
+ * @return the provisioning context
+ */
+ public ProvisioningContext getProvisioningContext() {
+ return context;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.equinox.p2.operations.IProfileChangeJob#getProfileId()
+ */
+ public String getProfileId() {
+ return profileId;
+ }
+
+ /**
+ * Return a boolean indicating whether the operation has been resolved. This method
+ * should be used to determine whether a client can expect to retrieve a profile
+ * change request, provisioning plan, or resolution result. It is possible that this
+ * method return <code>false</code> while resolution is taking place if it is performed
+ * in the background.
+ *
+ * @return <code>true</code> if the operation has been resolved, <code>false</code>
+ * if it has not resolved.
+ */
+ public boolean hasResolved() {
+ return getResolutionResult() != null;
+ }
+
+}
diff --git a/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/p2/operations/ProfileModificationJob.java b/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/p2/operations/ProfileModificationJob.java
new file mode 100644
index 000000000..24a3bf888
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/p2/operations/ProfileModificationJob.java
@@ -0,0 +1,114 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2009 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
+ *******************************************************************************/
+package org.eclipse.equinox.p2.operations;
+
+import org.eclipse.core.runtime.*;
+import org.eclipse.equinox.internal.p2.engine.PhaseSet;
+import org.eclipse.equinox.p2.engine.*;
+
+/**
+ * A job that modifies a profile according to a specified provisioning plan.
+ *
+ * @since 2.0
+ * @noextend This class is not intended to be subclassed by clients.
+ */
+public class ProfileModificationJob extends ProvisioningJob implements IProfileChangeJob {
+
+ IProvisioningPlan plan;
+ String profileId;
+ PhaseSet phaseSet = new DefaultPhaseSet();
+ ProvisioningContext provisioningContext;
+ int restartPolicy = ProvisioningJob.RESTART_OR_APPLY;
+ private String taskName;
+
+ /**
+ * Create a job that will update a profile according to the specified provisioning plan.
+ *
+ * @param name the name of the job
+ * @param session the provisioning session to use to obtain provisioning services
+ * @param profileId the id of the profile to be altered
+ * @param plan the provisioning plan describing how the profile is to be altered
+ * @param context the provisioning context describing how the operation is to be performed
+ */
+ public ProfileModificationJob(String name, ProvisioningSession session, String profileId, IProvisioningPlan plan, ProvisioningContext context) {
+ super(name, session);
+ this.plan = plan;
+ this.profileId = profileId;
+ this.provisioningContext = context;
+ }
+
+ /**
+ * Set the phase set to be used when running the provisioning plan. This method need only
+ * be used when the default phase set is not sufficient. For example, clients could
+ * use this method to perform a sizing or to download artifacts without provisioning them.
+ *
+ * @param phaseSet the provisioning phases to be run during provisioning.
+ */
+ public void setPhaseSet(PhaseSet phaseSet) {
+ this.phaseSet = phaseSet;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.equinox.p2.operations.IProfileChangeJob#getProfileId()
+ */
+ public String getProfileId() {
+ return profileId;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.equinox.p2.operations.ProvisioningJob#runModal(org.eclipse.core.runtime.IProgressMonitor)
+ */
+ public IStatus runModal(IProgressMonitor monitor) {
+ String task = taskName;
+ IStatus status = Status.OK_STATUS;
+ if (task == null)
+ task = getName();
+ monitor.beginTask(task, 1000);
+ try {
+ status = getSession().performProvisioningPlan(plan, phaseSet, provisioningContext, new SubProgressMonitor(monitor, 1000));
+ } finally {
+ monitor.done();
+ }
+ return status;
+ }
+
+ /**
+ * Sets the top level task name for progress when running this operation.
+ *
+ * @param label the label to be used for the task name
+ */
+ public void setTaskName(String label) {
+ this.taskName = label;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.equinox.p2.operations.ProvisioningJob#getRestartPolicy()
+ */
+ public int getRestartPolicy() {
+ return restartPolicy;
+ }
+
+ /**
+ * Set the restart policy that describes whether restart is needed after
+ * performing this job.
+ *
+ * @param policy an integer describing the restart policy
+ * @see ProvisioningJob#RESTART_NONE
+ * @see ProvisioningJob#RESTART_ONLY
+ * @see ProvisioningJob#RESTART_OR_APPLY
+ */
+ public void setRestartPolicy(int policy) {
+ restartPolicy = policy;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/p2/operations/ProvisioningJob.java b/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/p2/operations/ProvisioningJob.java
new file mode 100644
index 000000000..3e834b4a5
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/p2/operations/ProvisioningJob.java
@@ -0,0 +1,227 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2009 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
+ *******************************************************************************/
+package org.eclipse.equinox.p2.operations;
+
+import org.eclipse.equinox.p2.core.ProvisionException;
+
+import org.eclipse.core.runtime.*;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.equinox.internal.p2.operations.Activator;
+import org.eclipse.equinox.internal.p2.operations.Messages;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * Abstract class representing provisioning jobs. Provisioning jobs
+ * can be run in the background by scheduling them, or they can
+ * be run by a client in a modal context. An additional progress monitor
+ * can be set into the job for progress reporting.
+ *
+ * @since 2.0
+ */
+public abstract class ProvisioningJob extends Job {
+
+ /**
+ * Class for multiplexing progress across multiple progress monitors.
+ */
+ private static class DoubleProgressMonitor extends ProgressMonitorWrapper {
+
+ IProgressMonitor additionalMonitor;
+
+ protected DoubleProgressMonitor(IProgressMonitor monitor1, IProgressMonitor monitor2) {
+ super(monitor1);
+ additionalMonitor = monitor2;
+ }
+
+ public void beginTask(String name, int totalWork) {
+ super.beginTask(name, totalWork);
+ additionalMonitor.beginTask(name, totalWork);
+ }
+
+ public void clearBlocked() {
+ super.clearBlocked();
+ if (additionalMonitor instanceof IProgressMonitorWithBlocking)
+ ((IProgressMonitorWithBlocking) additionalMonitor).clearBlocked();
+ }
+
+ public void done() {
+ super.done();
+ additionalMonitor.done();
+ }
+
+ public void internalWorked(double work) {
+ super.internalWorked(work);
+ additionalMonitor.internalWorked(work);
+ }
+
+ public boolean isCanceled() {
+ if (super.isCanceled())
+ return true;
+ return additionalMonitor.isCanceled();
+ }
+
+ public void setBlocked(IStatus reason) {
+ super.setBlocked(reason);
+ if (additionalMonitor instanceof IProgressMonitorWithBlocking)
+ ((IProgressMonitorWithBlocking) additionalMonitor).setBlocked(reason);
+ }
+
+ public void setCanceled(boolean b) {
+ super.setCanceled(b);
+ additionalMonitor.setCanceled(b);
+ }
+
+ public void setTaskName(String name) {
+ super.setTaskName(name);
+ additionalMonitor.setTaskName(name);
+ }
+
+ public void subTask(String name) {
+ super.subTask(name);
+ additionalMonitor.subTask(name);
+ }
+
+ public void worked(int work) {
+ super.worked(work);
+ additionalMonitor.worked(work);
+ }
+ }
+
+ /**
+ * Constant which indicates that the job does not require a restart
+ * upon completion. This constant is typically used for operations that
+ * do not modify the running profile.
+ *
+ * @since 2.0
+ */
+ public static final int RESTART_NONE = 1;
+
+ /**
+ * Constant which indicates that the job requires the user to either
+ * restart or apply the configuration changes in order to pick up the
+ * changes performed by the job. This constant is typically used for
+ * operations that modify the running profile.
+ *
+ * @since 2.0
+ */
+ public static final int RESTART_OR_APPLY = 2;
+ /**
+ * Constant which indicates that the job requires the user to restart
+ * in order to pick up the changes performed by the job. This constant
+ * is typically used for operations that modify the running profile but don't
+ * handle dynamic changes without restarting the workbench.
+ *
+ * @since 2.0
+ */
+ public static final int RESTART_ONLY = 3;
+
+ private ProvisioningSession session;
+ private IProgressMonitor additionalMonitor;
+
+ /**
+ * Create a provisioning job with the given name that uses the
+ * provided provisioning session for retrieving any services
+ * needed.
+ *
+ * @param name the name of the job
+ * @param session the session providing the services
+ */
+ public ProvisioningJob(String name, ProvisioningSession session) {
+ super(name);
+ this.session = session;
+ }
+
+ /**
+ * Return the provisioning session that is used by the receiver
+ * when retrieving necessary provisioning services.
+ *
+ * @return the session
+ * @see ProvisioningSession
+ */
+ protected ProvisioningSession getSession() {
+ return session;
+ }
+
+ private IProgressMonitor getCombinedProgressMonitor(IProgressMonitor mon1, IProgressMonitor mon2) {
+ if (mon1 == null)
+ return mon2;
+ if (mon2 == null)
+ return mon1;
+ return new DoubleProgressMonitor(mon1, mon2);
+ }
+
+ public void setAdditionalProgressMonitor(IProgressMonitor monitor) {
+ additionalMonitor = monitor;
+ }
+
+ /**
+ * Executes this job. Returns the result of the execution.
+ * This method is overridden to look for a wrapped progress monitor for
+ * reporting progress.
+ *
+ * @noreference This method is not intended to be referenced by clients.
+ * @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor)
+ *
+ */
+ public final IStatus run(IProgressMonitor monitor) {
+ IProgressMonitor wrappedMonitor = getCombinedProgressMonitor(monitor, additionalMonitor);
+ IStatus status = Status.OK_STATUS;
+ try {
+ status = runModal(wrappedMonitor);
+ } catch (OperationCanceledException e) {
+ status = Status.CANCEL_STATUS;
+ }
+ return status;
+ }
+
+ /**
+ * Perform the specific work involved in running this job in
+ * the current thread. This method can be called directly by
+ * clients, or in the course of running the job in the
+ * background.
+ *
+ * @param monitor
+ * the progress monitor to use for the operation
+ *
+ * @return a status indicating the result of the operation.
+ *
+ */
+ public abstract IStatus runModal(IProgressMonitor monitor);
+
+ /**
+ * Return the restart policy that is appropriate for this job.
+ *
+ * @return a constant indicating the restart policy
+ *
+ * @see #RESTART_NONE
+ * @see #RESTART_ONLY
+ * @see #RESTART_OR_APPLY
+ */
+ public int getRestartPolicy() {
+ return RESTART_NONE;
+ }
+
+ /**
+ * Return an error status that can be used to report the specified exception.
+ *
+ * @param message the message that should be used in the status
+ * @param e the exception to be reported
+ * @return a status that can be used to describe the exception
+ */
+ protected IStatus getErrorStatus(String message, ProvisionException e) {
+ if (message == null)
+ if (e == null)
+ message = NLS.bind(Messages.ProvisioningJob_GenericErrorStatusMessage, getName());
+ else
+ message = e.getLocalizedMessage();
+ return new Status(IStatus.ERROR, Activator.ID, message, e);
+ }
+
+}
diff --git a/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/p2/operations/ProvisioningSession.java b/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/p2/operations/ProvisioningSession.java
new file mode 100644
index 000000000..9a91bdc5a
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/p2/operations/ProvisioningSession.java
@@ -0,0 +1,317 @@
+/*******************************************************************************
+ * Copyright (c) 2009 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
+ ******************************************************************************/
+
+package org.eclipse.equinox.p2.operations;
+
+import java.io.IOException;
+import java.util.*;
+import org.eclipse.core.runtime.*;
+import org.eclipse.core.runtime.jobs.*;
+import org.eclipse.equinox.internal.p2.core.helpers.ServiceHelper;
+import org.eclipse.equinox.internal.p2.engine.PhaseSet;
+import org.eclipse.equinox.internal.p2.operations.*;
+import org.eclipse.equinox.internal.provisional.configurator.Configurator;
+import org.eclipse.equinox.internal.provisional.p2.core.eventbus.IProvisioningEventBus;
+import org.eclipse.equinox.internal.provisional.p2.director.IPlanner;
+import org.eclipse.equinox.internal.provisional.p2.director.ProfileChangeRequest;
+import org.eclipse.equinox.p2.core.IAgentLocation;
+import org.eclipse.equinox.p2.core.IProvisioningAgent;
+import org.eclipse.equinox.p2.engine.*;
+import org.eclipse.equinox.p2.engine.query.UserVisibleRootQuery;
+import org.eclipse.equinox.p2.metadata.IInstallableUnit;
+import org.eclipse.equinox.p2.metadata.query.InstallableUnitQuery;
+import org.eclipse.equinox.p2.query.*;
+import org.eclipse.equinox.p2.repository.artifact.IArtifactRepositoryManager;
+import org.eclipse.equinox.p2.repository.metadata.IMetadataRepositoryManager;
+
+/**
+ * ProvisioningSession provides the context for a provisioning session, including
+ * the provisioning services that should be used. It also provides utility
+ * methods for commonly performed provisioning tasks.
+ *
+ * @since 2.0
+ * @noextend This class is not intended to be subclassed by clients.
+ */
+public class ProvisioningSession {
+ private IProvisioningAgent agent;
+
+ private Set<Job> scheduledJobs = Collections.synchronizedSet(new HashSet<Job>());
+
+ /**
+ * A constant indicating that there was nothing to size (there
+ * was no valid plan that could be used to compute
+ * size).
+ */
+ public static final long SIZE_NOTAPPLICABLE = -3L;
+ /**
+ * Indicates that the size is unavailable (an
+ * attempt was made to compute size but it failed)
+ */
+ public static final long SIZE_UNAVAILABLE = -2L;
+ /**
+ * Indicates that the size is currently unknown
+ */
+ public static final long SIZE_UNKNOWN = -1L;
+
+ /**
+ * 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;
+
+ /**
+ * A status code used to indicate that a repository location was not valid.
+ */
+ public static final int STATUS_INVALID_REPOSITORY_LOCATION = IStatusCodes.INVALID_REPOSITORY_LOCATION;
+
+ /**
+ * Create a provisioning session using the services of the supplied agent.
+ * @param agent the provisioning agent that supplies services. Must not be <code>null</code>.
+ */
+ public ProvisioningSession(IProvisioningAgent agent) {
+ Assert.isNotNull(agent, Messages.ProvisioningSession_AgentNotFound);
+ this.agent = agent;
+ }
+
+ /**
+ * Return the provisioning agent used to retrieve provisioning services.
+ * @return the provisioning agent
+ */
+ public IProvisioningAgent getProvisioningAgent() {
+ return agent;
+ }
+
+ /**
+ * Return the agent location for this session
+ * @return the agent location
+ */
+ public IAgentLocation getAgentLocation() {
+ return (IAgentLocation) agent.getService(IAgentLocation.SERVICE_NAME);
+ }
+
+ /**
+ * Return the artifact repository manager for this session
+ * @return the repository manager
+ */
+ public IArtifactRepositoryManager getArtifactRepositoryManager() {
+ return (IArtifactRepositoryManager) agent.getService(IArtifactRepositoryManager.SERVICE_NAME);
+ }
+
+ /**
+ * Return the metadata repository manager for this session
+ * @return the repository manager
+ */
+ public IMetadataRepositoryManager getMetadataRepositoryManager() {
+ return (IMetadataRepositoryManager) agent.getService(IMetadataRepositoryManager.SERVICE_NAME);
+ }
+
+ /**
+ * Return the profile registry for this session
+ * @return the profile registry
+ */
+ public IProfileRegistry getProfileRegistry() {
+ return (IProfileRegistry) agent.getService(IProfileRegistry.SERVICE_NAME);
+ }
+
+ /**
+ * Return the provisioning engine for this session
+ * @return the provisioning engine
+ */
+ public IEngine getEngine() {
+ return (IEngine) agent.getService(IEngine.SERVICE_NAME);
+ }
+
+ /**
+ * Return the provisioning event bus used for dispatching events.
+ * @return the event bus
+ */
+ public IProvisioningEventBus getProvisioningEventBus() {
+ return (IProvisioningEventBus) agent.getService(IProvisioningEventBus.SERVICE_NAME);
+ }
+
+ /**
+ * Return the planner used for this session
+ * @return the planner
+ */
+ public IPlanner getPlanner() {
+ return (IPlanner) agent.getService(IPlanner.SERVICE_NAME);
+ }
+
+ /**
+ * Get sizing information about the specified plan.
+ *
+ * @param plan the provisioning plan
+ * @param context the provisioning context to be used for the sizing
+ * @param monitor the progress monitor
+ *
+ * @return a long integer describing the disk size required for the provisioning plan.
+ *
+ * @see #SIZE_UNKNOWN
+ * @see #SIZE_UNAVAILABLE
+ * @see #SIZE_NOTAPPLICABLE
+ */
+ public long getSize(IProvisioningPlan plan, ProvisioningContext context, IProgressMonitor monitor) {
+ // If there is nothing to size, return 0
+ if (plan == null)
+ return SIZE_NOTAPPLICABLE;
+ if (countPlanElements(plan) == 0)
+ return 0;
+ long installPlanSize = 0;
+ SubMonitor mon = SubMonitor.convert(monitor, 300);
+ if (plan.getInstallerPlan() != null) {
+ SizingPhaseSet set = new SizingPhaseSet();
+ IStatus status = getEngine().perform(plan.getInstallerPlan(), set, mon.newChild(100));
+ if (status.isOK())
+ installPlanSize = set.getSizing().getDiskSize();
+ } else {
+ mon.worked(100);
+ }
+ SizingPhaseSet set = new SizingPhaseSet();
+ IStatus status = getEngine().perform(plan, set, mon.newChild(200));
+ if (status.isOK())
+ return installPlanSize + set.getSizing().getDiskSize();
+ return SIZE_UNAVAILABLE;
+ }
+
+ private int countPlanElements(IProvisioningPlan plan) {
+ return new CompoundQueryable<IInstallableUnit>(plan.getAdditions(), plan.getRemovals()).query(InstallableUnitQuery.ANY, null).unmodifiableSet().size();
+ }
+
+ /**
+ * Perform the specified provisioning plan.
+ *
+ * @param plan the provisioning plan to be performed
+ * @param phaseSet the phase set to be used for the plan
+ * @param context the provisioning context to be used during provisioning
+ * @param monitor the progress monitor to use while performing the plan
+ * @return a status describing the result of performing the plan
+ */
+ public IStatus performProvisioningPlan(IProvisioningPlan plan, PhaseSet phaseSet, ProvisioningContext context, IProgressMonitor monitor) {
+ PhaseSet set;
+ if (phaseSet == null)
+ set = new DefaultPhaseSet();
+ else
+ set = phaseSet;
+
+ // 300 ticks for download, 100 to install handlers, 100 to compute the plan, 100 to install the rest
+ SubMonitor mon = SubMonitor.convert(monitor, 600);
+ int ticksUsed = 0;
+
+ // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=272355
+ // The exact profile instance used in the profile change request and passed to the engine must be used for all
+ // of these operations, otherwise we can get profile out of synch errors.
+ IProfile profile = plan.getProfile();
+
+ if (plan.getInstallerPlan() != null) {
+ if (set instanceof DefaultPhaseSet) {
+ // If the phase set calls for download and install, then we want to download everything atomically before
+ // applying the install plan. This way, we can be sure to install the install handler only if we know
+ // we will be able to get everything else.
+ ProfileChangeRequest downloadRequest = new ProfileChangeRequest(profile);
+ downloadRequest.setAbsoluteMode(true);
+ downloadRequest.addInstallableUnits(new CompoundQueryable<IInstallableUnit>(plan.getAdditions(), plan.getInstallerPlan().getAdditions()).query(InstallableUnitQuery.ANY, null));
+
+ PhaseSet download = new DownloadPhaseSet();
+ IProvisioningPlan downloadPlan = getPlanner().getProvisioningPlan(downloadRequest, context, mon.newChild(100));
+ IStatus downloadStatus = getEngine().perform(downloadPlan, download, mon.newChild(300));
+ if (!downloadStatus.isOK()) {
+ mon.done();
+ return downloadStatus;
+ }
+ ticksUsed = 300;
+ }
+ // we pre-downloaded if necessary. Now perform the plan against the original phase set.
+ IStatus installerPlanStatus = getEngine().perform(plan.getInstallerPlan(), set, mon.newChild(100));
+ if (!installerPlanStatus.isOK()) {
+ mon.done();
+ return installerPlanStatus;
+ }
+ ticksUsed += 100;
+ // Apply the configuration
+ Configurator configChanger = (Configurator) ServiceHelper.getService(Activator.getContext(), Configurator.class.getName());
+ try {
+ configChanger.applyConfiguration();
+ } catch (IOException e) {
+ mon.done();
+ return new Status(IStatus.ERROR, Activator.ID, Messages.ProvisioningSession_InstallPlanConfigurationError, e);
+ }
+ }
+ return getEngine().perform(plan, set, mon.newChild(500 - ticksUsed));
+ }
+
+ /**
+ * Return a boolean indicating whether any other provisioning operations are
+ * scheduled for the specified profile.
+ *
+ * @param profileId the id of the profile in question
+ * @return <code>true</code> if there are pending provisioning operations for
+ * this profile, <code>false</code> if there are not.
+ * @see #rememberJob(Job)
+ */
+ public boolean hasScheduledOperationsFor(String profileId) {
+ Job[] jobs = getScheduledJobs();
+ for (int i = 0; i < jobs.length; i++) {
+ if (jobs[i] instanceof IProfileChangeJob) {
+ String id = ((IProfileChangeJob) jobs[i]).getProfileId();
+ if (profileId.equals(id))
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private Job[] getScheduledJobs() {
+ synchronized (scheduledJobs) {
+ return scheduledJobs.toArray(new Job[scheduledJobs.size()]);
+ }
+ }
+
+ /**
+ * Remember the specified job. Remembered jobs are
+ * checked when callers want to know what work is scheduled for
+ * a particular profile.
+ *
+ * @param job the job to be remembered
+ * @see #hasScheduledOperationsFor(String)
+ */
+ public void rememberJob(Job job) {
+ scheduledJobs.add(job);
+ job.addJobChangeListener(new JobChangeAdapter() {
+ public void done(IJobChangeEvent event) {
+ scheduledJobs.remove(event.getJob());
+ }
+ });
+ }
+
+ /**
+ * 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.
+ */
+ public IInstallableUnit[] getInstalledIUs(String profileId, boolean all) {
+ IProfile profile = getProfileRegistry().getProfile(profileId);
+ if (profile == null)
+ return new IInstallableUnit[0];
+ IQuery<IInstallableUnit> query;
+ if (all)
+ query = InstallableUnitQuery.ANY;
+ else
+ query = new UserVisibleRootQuery();
+ IQueryResult<IInstallableUnit> queryResult = profile.query(query, null);
+ return queryResult.toArray(IInstallableUnit.class);
+ }
+
+}
diff --git a/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/p2/operations/RepositoryTracker.java b/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/p2/operations/RepositoryTracker.java
new file mode 100644
index 000000000..8ef818e3c
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/p2/operations/RepositoryTracker.java
@@ -0,0 +1,275 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2009 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
+ *******************************************************************************/
+package org.eclipse.equinox.p2.operations;
+
+import org.eclipse.equinox.p2.core.ProvisionException;
+
+import java.io.File;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.*;
+import org.eclipse.core.runtime.*;
+import org.eclipse.equinox.internal.p2.core.helpers.LogHelper;
+import org.eclipse.equinox.internal.p2.operations.*;
+import org.eclipse.equinox.internal.p2.repository.helpers.RepositoryHelper;
+import org.eclipse.equinox.p2.repository.IRepositoryManager;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * RepositoryTracker defines a service that retrieves repositories, tracks their status, and
+ * reports errors.
+ *
+ * @since 2.0
+ */
+public abstract class RepositoryTracker {
+
+ // What repositories to show
+ private int artifactRepositoryFlags = IRepositoryManager.REPOSITORIES_NON_SYSTEM;
+ private int metadataRepositoryFlags = IRepositoryManager.REPOSITORIES_NON_SYSTEM;
+ /**
+ * List<URI> of repositories that have already been reported to the user as not found.
+ */
+ private final List<URI> reposNotFound = Collections.synchronizedList(new ArrayList<URI>());
+
+ /**
+ * Return an array of repository locations known for the specified provisioning session.
+ *
+ * @param session the provisioning session providing the provisioning services
+ * @return an array of repository locations known by this tracker
+ */
+ public abstract URI[] getKnownRepositories(ProvisioningSession session);
+
+ /**
+ * Return a status appropriate for reporting an invalid repository location.
+ * @param locationText the text representation of the location
+ * @return a status that describes an invalid location
+ */
+ public IStatus getInvalidLocationStatus(String locationText) {
+ return new Status(IStatus.ERROR, Activator.ID, IStatusCodes.INVALID_REPOSITORY_LOCATION, NLS.bind(Messages.RepositoryTracker_InvalidLocation, locationText), null);
+ }
+
+ /**
+ * Return a repository location represented by the supplied string. The provided
+ * string should either be an unencoded string representation of a URI, or a
+ * local file system path. This method is generally suitable for converting a
+ * location string entered by an end user into a suitable URI representation.
+ *
+ * @param locationString a text representation of the location
+ * @return a repository location URI, or <code>null</code> if the
+ * text could not be interpreted.
+ */
+ public URI locationFromString(String locationString) {
+ URI userLocation;
+ try {
+ userLocation = URIUtil.fromString(locationString);
+ } catch (URISyntaxException e) {
+ return null;
+ }
+ // If a path separator char was used, interpret as a local file URI
+ String uriString = URIUtil.toUnencodedString(userLocation);
+ if (uriString.length() > 0 && (uriString.charAt(0) == '/' || uriString.charAt(0) == File.separatorChar))
+ return RepositoryHelper.localRepoURIHelper(userLocation);
+ return userLocation;
+ }
+
+ /**
+ * Validate the specified repository location.
+ *
+ * @param session the provisioning session providing the repository services
+ * @param location the location in question
+ * @param contactRepositories <code>true</code> if the appropriate repository manager(s) should be
+ * consulted regarding the validity of the location, or <code>false</code> if the repository manager
+ * should not be consulted.
+ * @param monitor the progress monitor
+ * @return a status indicating the current status of the repository
+ */
+
+ public IStatus validateRepositoryLocation(ProvisioningSession session, URI location, boolean contactRepositories, IProgressMonitor monitor) {
+ // First validate syntax issues
+ IStatus localValidationStatus = RepositoryHelper.checkRepositoryLocationSyntax(location);
+ if (!localValidationStatus.isOK()) {
+ // bad syntax, but it could just be non-absolute.
+ // In this case, use the helper
+ String locationString = URIUtil.toUnencodedString(location);
+ if (locationString.length() > 0 && (locationString.charAt(0) == '/' || locationString.charAt(0) == File.separatorChar)) {
+ location = RepositoryHelper.localRepoURIHelper(location);
+ localValidationStatus = RepositoryHelper.checkRepositoryLocationSyntax(location);
+ }
+ }
+
+ if (!localValidationStatus.isOK())
+ return localValidationStatus;
+
+ // Syntax was ok, now look for duplicates
+ URI[] knownRepositories = getKnownRepositories(session);
+ for (int i = 0; i < knownRepositories.length; i++) {
+ if (URIUtil.sameURI(knownRepositories[i], location)) {
+ localValidationStatus = new Status(IStatus.ERROR, Activator.ID, IStatusCodes.INVALID_REPOSITORY_LOCATION, Messages.RepositoryTracker_DuplicateLocation, null);
+ break;
+ }
+ }
+
+ if (!localValidationStatus.isOK())
+ return localValidationStatus;
+
+ if (contactRepositories)
+ return validateRepositoryLocationWithManager(session, location, monitor);
+
+ return localValidationStatus;
+ }
+
+ /**
+ * Validate the specified repository location using the appropriate repository manager.
+ *
+ * @param session the provisioning session providing the repository services
+ * @param location the location in question
+ * @param monitor the progress monitor
+ * @return a status indicating the current status of the repository
+ */
+ protected abstract IStatus validateRepositoryLocationWithManager(ProvisioningSession session, URI location, IProgressMonitor monitor);
+
+ /**
+ * Add the specified location to the list of "not found" repositories.
+ * This list is used to ensure that errors are not reported multiple times
+ * for the same repository.
+ *
+ * The caller is already assumed to have reported any errors if necessary.
+ *
+ * @param location the location of the repository that cannot be found
+ */
+ public void addNotFound(URI location) {
+ reposNotFound.add(location);
+ }
+
+ /**
+ * Answer a boolean indicating whether not found status has already been
+ * reported for the specified location.
+ *
+ * @param location the location in question
+ * @return <code>true</code> if the repository has already been reported as
+ * being not found, <code>false</code> if no status has been reported for this
+ * location.
+ */
+ public boolean hasNotFoundStatusBeenReported(URI location) {
+ // We don't check for things like case variants or end slash variants
+ // because we know that the repository managers already did this.
+ return reposNotFound.contains(location);
+ }
+
+ /**
+ * Clear the list of repositories that have already been reported as not found.
+ */
+ public void clearRepositoriesNotFound() {
+ reposNotFound.clear();
+ }
+
+ /**
+ * Remove the specified repository from the list of repositories that
+ * have already been reported as not found. This method has no effect
+ * if the repository has never been reported as not found.
+ *
+ * @param location the location in question
+ */
+ public void clearRepositoryNotFound(URI location) {
+ reposNotFound.remove(location);
+ }
+
+ /**
+ * Return the repository flags suitable for retrieving known repositories from
+ * a repository manager
+ *
+ * @return the repository flags
+ *
+ */
+ public int getArtifactRepositoryFlags() {
+ return artifactRepositoryFlags;
+ }
+
+ /**
+ * Set the repository flags suitable for retrieving known repositories from
+ * a repository manager
+ *
+ * @param flags the repository flags
+ *
+ */
+ public void setArtifactRepositoryFlags(int flags) {
+ artifactRepositoryFlags = flags;
+ }
+
+ /**
+ * Return the repository flags suitable for retrieving known repositories from
+ * a repository manager
+ *
+ * @return the repository flags
+ *
+ */
+ public int getMetadataRepositoryFlags() {
+ return metadataRepositoryFlags;
+ }
+
+ /**
+ * Set the repository flags suitable for retrieving known repositories from
+ * a repository manager
+ *
+ * @param flags the repository flags
+ *
+ */
+
+ public void setMetadataRepositoryFlags(int flags) {
+ metadataRepositoryFlags = flags;
+ }
+
+ /**
+ * Report a failure to load the specified repository.
+ * <p>
+ * This default implementation simply logs the failure. Subclasses may override
+ * to provide additional error reporting.
+ * </p>
+ * @param location the location of the failed repository
+ * @param exception the failure that occurred
+ */
+ public void reportLoadFailure(final URI location, ProvisionException exception) {
+ // special handling when the repo location is bad. We don't want to continually report it
+ int code = exception.getStatus().getCode();
+ if (code == IStatusCodes.INVALID_REPOSITORY_LOCATION || code == ProvisionException.REPOSITORY_INVALID_LOCATION || code == ProvisionException.REPOSITORY_NOT_FOUND) {
+ if (hasNotFoundStatusBeenReported(location))
+ return;
+ addNotFound(location);
+ }
+
+ LogHelper.log(exception.getStatus());
+ }
+
+ /**
+ * Add a repository at the specified location.
+ *
+ * @param location the location of the new repository
+ * @param nickname the nickname for the repository, or <code>null</code> if there is no nickname
+ * @param session the session to use for provisioning services
+ */
+ public abstract void addRepository(URI location, String nickname, ProvisioningSession session);
+
+ /**
+ * Remove the repositories at the specified locations
+ *
+ * @param locations the locations
+ * @param session the session to use for provisioning services
+ */
+ public abstract void removeRepositories(URI[] locations, ProvisioningSession session);
+
+ /**
+ * Refresh the repositories at the specified locations
+ * @param locations the locations
+ * @param session the session to use for provisioning services
+ * @param monitor the progress monitor to use
+ */
+ public abstract void refreshRepositories(URI[] locations, ProvisioningSession session, IProgressMonitor monitor);
+}
diff --git a/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/p2/operations/UninstallOperation.java b/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/p2/operations/UninstallOperation.java
new file mode 100644
index 000000000..1de60db2c
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/p2/operations/UninstallOperation.java
@@ -0,0 +1,85 @@
+/*******************************************************************************
+ * Copyright (c) 2009 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
+ ******************************************************************************/
+
+package org.eclipse.equinox.p2.operations;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.MultiStatus;
+import org.eclipse.equinox.internal.p2.operations.Messages;
+import org.eclipse.equinox.internal.provisional.p2.director.ProfileChangeRequest;
+import org.eclipse.equinox.p2.engine.IProfile;
+import org.eclipse.equinox.p2.metadata.IInstallableUnit;
+
+/**
+ * An UninstallOperation describes an operation that uninstalls {@link IInstallableUnit}s from
+ * a profile.
+ *
+ * The following snippet shows how one might use an UninstallOperation to perform a synchronous resolution and
+ * then kick off an uninstall in the background:
+ *
+ * <pre>
+ * UninstallOperation op = new UninstallOperation(session, new IInstallableUnit [] { removeThisIU });
+ * IStatus result = op.resolveModal(monitor);
+ * if (result.isOK()) {
+ * op.getProvisioningJob(monitor).schedule();
+ * }
+ * </pre>
+ * @noextend This class is not intended to be subclassed by clients.
+ * @since 2.0
+ */
+public class UninstallOperation extends ProfileChangeOperation {
+
+ private IInstallableUnit[] toUninstall;
+
+ /**
+ * Create an uninstall operation on the specified provisioning session that uninstalls
+ * 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 toUninstall the IInstallableUnits to be installed into the profile.
+ */
+ public UninstallOperation(ProvisioningSession session, IInstallableUnit[] toUninstall) {
+ super(session);
+ this.toUninstall = toUninstall;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.equinox.p2.operations.ProfileChangeOperation#computeProfileChangeRequest(org.eclipse.core.runtime.IProgressMonitor)
+ */
+ protected void computeProfileChangeRequest(MultiStatus status, IProgressMonitor monitor) {
+ request = ProfileChangeRequest.createByProfileId(profileId);
+ request.removeInstallableUnits(toUninstall);
+ // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=255984
+ // We ask to remove the the profile root property in addition to removing the IU. In theory this
+ // should be redundant, but there are cases where the planner decides not to uninstall something because
+ // it is needed by others. We still want to remove the root in this case.
+ // if (rootMarkerKey != null)
+ for (int i = 0; i < toUninstall.length; i++)
+ request.removeInstallableUnitProfileProperty(toUninstall[i], IProfile.PROP_PROFILE_ROOT_IU);
+
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.equinox.p2.operations.ProfileChangeOperation#getProvisioningJobName()
+ */
+ protected String getProvisioningJobName() {
+ return Messages.UninstallOperation_ProvisioningJobName;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.equinox.p2.operations.ProfileChangeOperation#getResolveJobName()
+ */
+ protected String getResolveJobName() {
+ return Messages.UninstallOperation_ResolveJobName;
+ }
+
+}
diff --git a/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/p2/operations/Update.java b/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/p2/operations/Update.java
new file mode 100644
index 000000000..8188710e3
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/p2/operations/Update.java
@@ -0,0 +1,72 @@
+/*******************************************************************************
+ * Copyright (c) 2009 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
+ ******************************************************************************/
+
+package org.eclipse.equinox.p2.operations;
+
+import org.eclipse.equinox.p2.metadata.IInstallableUnit;
+
+/**
+ * A simple data structure describing a possible update.
+ *
+ * @noextend This class is not intended to be subclassed by clients.
+ * @since 2.0
+ */
+public class Update {
+
+ public IInstallableUnit toUpdate;
+ public IInstallableUnit replacement;
+
+ /**
+ * Creates a new update description.
+ * @param toUpdate The installable unit to update
+ * @param replacement The replacement installable unit
+ */
+ public Update(IInstallableUnit toUpdate, IInstallableUnit replacement) {
+ this.toUpdate = toUpdate;
+ this.replacement = replacement;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (!(obj instanceof Update))
+ return false;
+ if (toUpdate == null)
+ return false;
+ if (replacement == null)
+ return false;
+ Update other = (Update) obj;
+ return toUpdate.equals(other.toUpdate) && replacement.equals(other.replacement);
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Object#hashCode()
+ */
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((toUpdate == null) ? 0 : toUpdate.hashCode());
+ result = prime * result + ((replacement == null) ? 0 : replacement.hashCode());
+ return result;
+ }
+
+ /*(non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
+ public String toString() {
+ return "Update " + toUpdate.toString() + " ==> " + replacement.toString(); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/p2/operations/UpdateOperation.java b/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/p2/operations/UpdateOperation.java
new file mode 100644
index 000000000..13b295bb1
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/p2/operations/UpdateOperation.java
@@ -0,0 +1,277 @@
+/*******************************************************************************
+ * Copyright (c) 2009 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
+ ******************************************************************************/
+
+package org.eclipse.equinox.p2.operations;
+
+import java.util.*;
+import org.eclipse.core.runtime.*;
+import org.eclipse.equinox.internal.p2.operations.*;
+import org.eclipse.equinox.internal.provisional.p2.director.PlannerHelper;
+import org.eclipse.equinox.internal.provisional.p2.director.ProfileChangeRequest;
+import org.eclipse.equinox.p2.engine.IProfile;
+import org.eclipse.equinox.p2.metadata.IInstallableUnit;
+import org.eclipse.equinox.p2.metadata.query.InstallableUnitQuery;
+import org.eclipse.equinox.p2.metadata.query.PatchQuery;
+import org.eclipse.equinox.p2.query.IQueryResult;
+
+/**
+ * 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 {
+
+ private 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, 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 = 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
+ IInstallableUnit[] replacements = session.getPlanner().updatesFor(iu, context, monitor);
+ updates = new ArrayList<Update>(replacements.length);
+ for (int i = 0; i < replacements.length; i++) {
+ // 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.
+ IQueryResult<IInstallableUnit> alreadyInstalled = profile.query(new InstallableUnitQuery(replacements[i]), null);
+ if (alreadyInstalled.isEmpty()) {
+ Update update = new Update(iu, replacements[i]);
+ 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 = false;
+ IProfile profile = session.getProfileRegistry().getProfile(profileId);
+ if (profile == null)
+ return;
+
+ SubMonitor sub = SubMonitor.convert(monitor, Messages.UpdateOperation_ProfileChangeRequestProgress, 100 * iusToUpdate.length);
+ for (int i = 0; i < iusToUpdate.length; i++) {
+ SubMonitor iuMon = sub.newChild(100);
+ Update[] updates = updatesFor(iusToUpdate[i], profile, iuMon);
+ for (int j = 0; j < updates.length; j++) {
+ toBeUpdated.add(iusToUpdate[i]);
+ if (defaultUpdates != null && defaultUpdates.contains(updates[j])) {
+ elementsToPlan.add(updates[j]);
+ selectionSpecified = true;
+ }
+
+ }
+ 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 (PatchQuery.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(iusToUpdate[i].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(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.addInstallableUnits(theUpdate);
+ // if (rootMarkerKey != null)
+ request.setInstallableUnitProfileProperty(theUpdate, IProfile.PROP_PROFILE_ROOT_IU, Boolean.toString(true));
+ if (PatchQuery.isPatch(theUpdate)) {
+ request.setInstallableUnitInclusionRules(theUpdate, PlannerHelper.createOptionalInclusionRule(theUpdate));
+ } else {
+ request.removeInstallableUnit(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 = session.getInstalledIUs(profileId, false);
+ }
+ }
+
+}
diff --git a/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/p2/operations/package.html b/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/p2/operations/package.html
new file mode 100644
index 000000000..c0f0a930a
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.operations/src/org/eclipse/equinox/p2/operations/package.html
@@ -0,0 +1,33 @@
+<!doctype html public "-//w3c//dtd html 4.0 transitional//en">
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+ <meta name="Author" content="IBM">
+ <meta name="GENERATOR" content="Mozilla/4.5 [en] (WinNT; I) [Netscape]">
+ <title>Package-level Javadoc</title>
+</head>
+<body>
+Describes high level provisioning operations that can be resolved and performed
+either modally or in the background.
+<h2>
+Package Specification</h2>
+<p>
+This package consists of several kinds of operations and supporting classes:
+<ul>
+<li><b>Profile Change Operations</b> describe high level provisioning operations that
+modify a profile. These operations generally are performed in two phases, the resolution
+of the operation, followed by the provisioning of the resolved operation. Both the
+resolution and the provisioning tasks can be performed either synchronously or as jobs.
+</li>
+<li><b>Provisioning Jobs</b> describe lower level provisioning tasks that can
+be performed synchronously or in the background.
+</li>
+<li><b>ProvisioningSession</b> represents a particular instance of a p2 provisioning
+system. It provides access to all of the p2 core services as well as utility methods
+for commonly performed provisioning tasks.
+</li>
+</ul>
+<p>
+@since 2.0
+</body>
+</html>

Back to the top