/******************************************************************************* * 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); } }