diff options
Diffstat (limited to 'bundles/org.eclipse.equinox.p2.operations/src/org/eclipse')
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> |