blob: ec19235538c962afb95b9aad333c5bc7ae2e5bd6 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009, 2013 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.core.runtime.jobs.Job;
import org.eclipse.equinox.internal.p2.director.ProfileChangeRequest;
import org.eclipse.equinox.internal.p2.operations.*;
import org.eclipse.equinox.p2.engine.*;
import org.eclipse.equinox.p2.metadata.IInstallableUnit;
import org.eclipse.equinox.p2.planner.IProfileChangeRequest;
/**
* 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;
this.context = new ProvisioningContext(session.getProvisioningAgent());
}
/**
* 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) {
IStatus status = job.runModal(monitor);
if (status.getSeverity() == IStatus.CANCEL)
return Status.CANCEL_STATUS;
}
// For anything other than cancellation, we examine the artifacts of the resolution and come
// up with an overall summary.
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));
if (mon.isCanceled())
return null;
if (job != null)
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
}
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, getFirstPassProvisioningContext(), getSecondPassEvaluator(), 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 (request == null) {
if (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;
}
if (job != null && job.getResolutionResult() != null)
return job.getResolutionResult().getSummaryStatus();
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();
// We couldn't resolve, but we have some status describing
// why there is no profile change request.
IStatus result = getResolutionResult();
if (result != null)
return result.getMessage();
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()
* @since 2.1
*/
public IProfileChangeRequest getProfileChangeRequest() {
if (job != null)
return job.getProfileChangeRequest();
return null;
}
/**
* Return a provisioning job that can be used to perform the resolved operation. The job is
* created using the default values associated with a new job. It is up to clients to configure
* the priority of the job and set any appropriate properties, such as
* {@link Job#setUser(boolean)},
* {@link Job#setSystem(boolean)}, or {@link Job#setProperty(QualifiedName, Object)},
* before scheduling it.
*
* @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 is null we haven't resolved yet, so we must return null here
if (status == null)
return null;
if (status.getSeverity() != IStatus.CANCEL && status.getSeverity() != IStatus.ERROR) {
if (job.getProvisioningPlan() != null) {
ProfileModificationJob pJob = new ProfileModificationJob(getProvisioningJobName(), session, profileId, job.getProvisioningPlan(), job.getActualProvisioningContext());
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)
updateJobProvisioningContexts(job, 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;
}
ProvisioningContext getFirstPassProvisioningContext() {
return getProvisioningContext();
}
IFailedStatusEvaluator getSecondPassEvaluator() {
return new IFailedStatusEvaluator() {
public ProvisioningContext getSecondPassProvisioningContext(IProvisioningPlan failedPlan) {
return null;
}
};
}
protected void updateJobProvisioningContexts(PlannerResolutionJob job, ProvisioningContext context) {
job.setFirstPassProvisioningContext(context);
}
}