/******************************************************************************* * Copyright (c) 2008, 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 * Sonatype, Inc. - ongoing development * Red Hat, Inc. - support for remediation page * Ericsson (AB) - bug 409073 *******************************************************************************/ package org.eclipse.equinox.internal.p2.ui.dialogs; import java.lang.reflect.InvocationTargetException; import java.util.HashSet; import org.eclipse.core.runtime.*; import org.eclipse.core.runtime.jobs.*; import org.eclipse.equinox.internal.p2.ui.*; import org.eclipse.equinox.internal.p2.ui.model.ElementUtils; import org.eclipse.equinox.internal.p2.ui.model.IUElementListRoot; import org.eclipse.equinox.p2.engine.ProvisioningContext; import org.eclipse.equinox.p2.metadata.IInstallableUnit; import org.eclipse.equinox.p2.operations.*; import org.eclipse.equinox.p2.ui.*; import org.eclipse.jface.operation.IRunnableContext; import org.eclipse.jface.operation.IRunnableWithProgress; import org.eclipse.jface.wizard.*; import org.eclipse.swt.widgets.Composite; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.statushandlers.StatusManager; /** * Common superclass for a wizard that performs a provisioning * operation. * * @since 3.5 */ public abstract class ProvisioningOperationWizard extends Wizard { private static final String WIZARD_SETTINGS_SECTION = "WizardSettings"; //$NON-NLS-1$ protected ProvisioningUI ui; protected IUElementListRoot root; protected ProfileChangeOperation operation; protected Object[] planSelections; protected RemediationPage remediationPage; protected ISelectableIUsPage mainPage; protected IResolutionErrorReportingPage errorPage; protected ResolutionResultsWizardPage resolutionPage; private ProvisioningContext provisioningContext; protected LoadMetadataRepositoryJob repoPreloadJob; IStatus couldNotResolveStatus = Status.OK_STATUS; // we haven't tried and failed boolean resolveWithRelaxedConstraints = false; boolean waitingForOtherJobs = false; protected RemediationOperation remediationOperation; public ProvisioningOperationWizard(ProvisioningUI ui, ProfileChangeOperation operation, Object[] initialSelections, LoadMetadataRepositoryJob job) { super(); this.ui = ui; this.operation = operation; initializeResolutionModelElements(initialSelections); this.repoPreloadJob = job; setForcePreviousAndNextButtons(true); setNeedsProgressMonitor(true); if (operation != null) { provisioningContext = operation.getProvisioningContext(); } } public void setRemediationOperation(RemediationOperation remediationOperation) { this.remediationOperation = remediationOperation; } public RemediationOperation getRemediationOperation() { return remediationOperation; } /* * (non-Javadoc) * @see org.eclipse.jface.wizard.Wizard#addPages() */ @Override public void addPages() { mainPage = createMainPage(root, planSelections); addPage(mainPage); errorPage = createErrorReportingPage(); if (errorPage != mainPage) addPage(errorPage); remediationPage = createRemediationPage(); if (remediationPage != null) addPage(remediationPage); resolutionPage = createResolutionPage(); addPage(resolutionPage); } protected abstract RemediationPage createRemediationPage(); protected abstract IResolutionErrorReportingPage createErrorReportingPage(); protected abstract ISelectableIUsPage createMainPage(IUElementListRoot input, Object[] selections); protected abstract ResolutionResultsWizardPage createResolutionPage(); @Override public boolean performFinish() { return resolutionPage.performFinish(); } protected LoadMetadataRepositoryJob getRepositoryPreloadJob() { return repoPreloadJob; } /* * (non-Javadoc) * @see org.eclipse.jface.wizard.Wizard#getPreviousPage(org.eclipse.jface.wizard.IWizardPage) * */ @Override public IWizardPage getPreviousPage(IWizardPage page) { if (page == errorPage) { return mainPage; } return super.getPreviousPage(page); } /* * (non-Javadoc) * @see org.eclipse.jface.wizard.Wizard#getNextPage(org.eclipse.jface.wizard.IWizardPage) * */ @Override public IWizardPage getNextPage(IWizardPage page) { // If we are moving from the main page or error page, we may need to resolve before // advancing. if (page == remediationPage) { try { getContainer().run(true, true, new IRunnableWithProgress() { @Override public void run(IProgressMonitor monitor) { remediationOperation.setCurrentRemedy(remediationPage.getRemediationGroup().getCurrentRemedy()); remediationOperation.resolveModal(monitor); } }); } catch (InterruptedException e) { // Nothing to report if thread was interrupted } catch (InvocationTargetException e) { ProvUI.handleException(e.getCause(), null, StatusManager.SHOW | StatusManager.LOG); couldNotResolve(null); } operation = remediationOperation; initializeResolutionModelElements(ElementUtils.requestToElement(((RemediationOperation) operation).getCurrentRemedy(), !(this instanceof UpdateWizard))); planChanged(); return resolutionPage; } else if (page == mainPage || page == errorPage) { ISelectableIUsPage currentPage = (ISelectableIUsPage) page; // Do we need to resolve? if (operation == null || (operation != null && shouldRecomputePlan(currentPage))) { recomputePlan(getContainer(), true); } else { // the selections have not changed from an IU point of view, but we want // to reinitialize the resolution model elements to ensure they are up to // date. initializeResolutionModelElements(planSelections); } IStatus status = operation.getResolutionResult(); if (status == null || status.getSeverity() == IStatus.ERROR) { if (page == mainPage) { if (remediationOperation != null && remediationOperation.getResolutionResult() == Status.OK_STATUS && remediationOperation.getRemedyConfigs().length == 1) { planChanged(); return getNextPage(remediationPage); } if (remediationOperation != null && remediationOperation.getResolutionResult() == Status.OK_STATUS) { planChanged(); return remediationPage; } return errorPage; } } else if (status.getSeverity() == IStatus.CANCEL) { return page; } else { if (remediationPage != null) remediationPage.setPageComplete(true); return resolutionPage; } } return super.getNextPage(page); } /** * The selections that drive the provisioning operation have changed. We might need to * change the completion state of the resolution page. */ public void operationSelectionsChanged(ISelectableIUsPage page) { if (resolutionPage != null) { // If the page selections are different than what we may have resolved // against, then this page is not complete. boolean old = resolutionPage.isPageComplete(); resolutionPage.setPageComplete(page.getCheckedIUElements() != null && page.getCheckedIUElements().length > 0); // If the state has truly changed, update the buttons. if (old != resolutionPage.isPageComplete()) { IWizardContainer container = getContainer(); if (container != null && container.getCurrentPage() != null) getContainer().updateButtons(); } } } private boolean shouldRecomputePlan(ISelectableIUsPage page) { boolean previouslyWaiting = waitingForOtherJobs; boolean previouslyCanceled = getCurrentStatus().getSeverity() == IStatus.CANCEL; waitingForOtherJobs = ui.hasScheduledOperations(); return waitingForOtherJobs || previouslyWaiting || previouslyCanceled || pageSelectionsHaveChanged(page) || provisioningContextChanged(); } protected boolean pageSelectionsHaveChanged(ISelectableIUsPage page) { HashSet<IInstallableUnit> selectedIUs = new HashSet<>(); Object[] currentSelections = page.getCheckedIUElements(); selectedIUs.addAll(ElementUtils.elementsToIUs(currentSelections)); HashSet<IInstallableUnit> lastIUSelections = new HashSet<>(); if (planSelections != null) lastIUSelections.addAll(ElementUtils.elementsToIUs(planSelections)); return !(selectedIUs.equals(lastIUSelections)); } private boolean provisioningContextChanged() { ProvisioningContext currentProvisioningContext = getProvisioningContext(); if (currentProvisioningContext == null && provisioningContext == null) return false; if (currentProvisioningContext != null && provisioningContext != null) return !currentProvisioningContext.equals(provisioningContext); // One is null and the other is not return true; } protected void planChanged() { IWizardPage currentPage = getContainer().getCurrentPage(); if ((currentPage == null || currentPage == mainPage) && remediationPage != null && remediationOperation != null && remediationOperation.getResolutionResult() == Status.OK_STATUS) { remediationPage.updateStatus(root, operation, planSelections); } resolutionPage.updateStatus(root, operation); if (errorPage != resolutionPage) { IUElementListRoot newRoot = shouldUpdateErrorPageModelOnPlanChange() ? root : null; errorPage.updateStatus(newRoot, operation); } } protected boolean shouldUpdateErrorPageModelOnPlanChange() { return errorPage != mainPage; } protected abstract void initializeResolutionModelElements(Object[] selectedElements); protected ProvisioningContext getProvisioningContext() { if (operation != null) { return operation.getProvisioningContext(); } return new ProvisioningContext(ui.getSession().getProvisioningAgent()); } public void recomputePlan(IRunnableContext runnableContext) { recomputePlan(runnableContext, false); } public void computeRemediationOperation(ProfileChangeOperation op, ProvisioningUI ui, IProgressMonitor monitor) { SubMonitor sub = SubMonitor.convert(monitor, ProvUIMessages.ProvisioningOperationWizard_Remediation_Operation, RemedyConfig.getAllRemedyConfigs().length); monitor.setTaskName(ProvUIMessages.ProvisioningOperationWizard_Remediation_Operation); remediationOperation = new RemediationOperation(ui.getSession(), op.getProfileChangeRequest()); remediationOperation.resolveModal(monitor); sub.done(); } /** * Recompute the provisioning plan based on the items in the IUElementListRoot and the given provisioning context. * Report progress using the specified runnable context. This method may be called before the page is created. * * @param runnableContext */ public void recomputePlan(IRunnableContext runnableContext, final boolean withRemediation) { couldNotResolveStatus = Status.OK_STATUS; provisioningContext = getProvisioningContext(); operation = null; remediationOperation = null; initializeResolutionModelElements(getOperationSelections()); if (planSelections.length == 0) { couldNotResolve(ProvUIMessages.ResolutionWizardPage_NoSelections); } else { operation = getProfileChangeOperation(planSelections); operation.setProvisioningContext(provisioningContext); try { runnableContext.run(true, true, new IRunnableWithProgress() { @Override public void run(IProgressMonitor monitor) { operation.resolveModal(monitor); if (withRemediation) { IStatus status = operation.getResolutionResult(); if (remediationPage != null && shouldRemediate(status)) { computeRemediationOperation(operation, ui, monitor); } } } }); } catch (InterruptedException e) { // Nothing to report if thread was interrupted } catch (InvocationTargetException e) { ProvUI.handleException(e.getCause(), null, StatusManager.SHOW | StatusManager.LOG); couldNotResolve(null); } } planChanged(); } public boolean shouldRemediate(IStatus status) { if (status == null) return true; if (status.getSeverity() != IStatus.ERROR) return false; return true; } /* * Get the selections that drive the provisioning operation. */ protected Object[] getOperationSelections() { return mainPage.getCheckedIUElements(); } protected abstract ProfileChangeOperation getProfileChangeOperation(Object[] elements); void couldNotResolve(String message) { if (message != null) { couldNotResolveStatus = new Status(IStatus.ERROR, ProvUIActivator.PLUGIN_ID, message, null); } else { couldNotResolveStatus = new Status(IStatus.ERROR, ProvUIActivator.PLUGIN_ID, ProvUIMessages.ProvisioningOperationWizard_UnexpectedFailureToResolve, null); } StatusManager.getManager().handle(couldNotResolveStatus, StatusManager.LOG); } public IStatus getCurrentStatus() { if (statusOverridesOperation()) return couldNotResolveStatus; if (operation != null && operation.getResolutionResult() != null) return operation.getResolutionResult(); return couldNotResolveStatus; } public String getDialogSettingsSectionName() { return getClass().getName() + "." + WIZARD_SETTINGS_SECTION; //$NON-NLS-1$ } public void saveBoundsRelatedSettings() { IWizardPage[] pages = getPages(); for (int i = 0; i < pages.length; i++) { if (pages[i] instanceof ProvisioningWizardPage) ((ProvisioningWizardPage) pages[i]).saveBoundsRelatedSettings(); } } protected Policy getPolicy() { return ui.getPolicy(); } protected String getProfileId() { return ui.getProfileId(); } protected boolean shouldShowProvisioningPlanChildren() { return ProvUI.getQueryContext(getPolicy()).getShowProvisioningPlanChildren(); } /* * Overridden to start the preload job after page control creation. * This allows any listeners on repo events to be set up before a * batch load occurs. The job creator uses a property to indicate if * the job needs scheduling (the client may have already completed the job * before the UI was opened). * (non-Javadoc) * @see org.eclipse.jface.wizard.Wizard#createPageControls(org.eclipse.swt.widgets.Composite) */ @Override public void createPageControls(Composite pageContainer) { // We call this so that wizards ignore all repository eventing that occurs while the wizard is // open. Otherwise, we can get an add event when a repository loads its references that we // don't want to respond to. Since repo discovery events can be received asynchronously by the // manager, the subsequent add events generated by the manager aren't guaranteed to be synchronous, // even if our listener is synchronous. Thus, we can't fine-tune // the "ignore" window to a specific operation. // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=277265#c38 ui.signalRepositoryOperationStart(); super.createPageControls(pageContainer); if (repoPreloadJob != null) { if (repoPreloadJob.getProperty(LoadMetadataRepositoryJob.WIZARD_CLIENT_SHOULD_SCHEDULE) != null) { // job has not been scheduled. Set a listener so we can report accumulated errors and // schedule it. repoPreloadJob.addJobChangeListener(new JobChangeAdapter() { @Override public void done(IJobChangeEvent e) { asyncReportLoadFailures(); } }); repoPreloadJob.schedule(); } else { // job has been scheduled, might already be done. if (repoPreloadJob.getState() == Job.NONE) { // job is done, report failures found so far // do it asynchronously since we are in the middle of creation asyncReportLoadFailures(); } else { // job is waiting, sleeping, running, report failures when // it's done repoPreloadJob.addJobChangeListener(new JobChangeAdapter() { @Override public void done(IJobChangeEvent e) { asyncReportLoadFailures(); } }); } } } } /* * (non-Javadoc) * @see org.eclipse.jface.wizard.Wizard#dispose() */ @Override public void dispose() { ui.signalRepositoryOperationComplete(null, false); super.dispose(); } void asyncReportLoadFailures() { if (repoPreloadJob != null && getShell() != null && !getShell().isDisposed()) { getShell().getDisplay().asyncExec(new Runnable() { @Override public void run() { if (PlatformUI.isWorkbenchRunning() && getShell() != null && !getShell().isDisposed()) repoPreloadJob.reportAccumulatedStatus(); } }); } } /* * Return a boolean indicating whether the wizard's current status should override any detail * reported by the operation. */ public boolean statusOverridesOperation() { return false; } public void setRelaxedResolution(boolean value) { this.resolveWithRelaxedConstraints = value; } public boolean getRelaxedResoltion() { return this.resolveWithRelaxedConstraints; } }