| /******************************************************************************* |
| * Copyright (c) 2008, 2018 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * 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 java.util.stream.Stream; |
| 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.IProvisioningPlan; |
| 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.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; |
| private IProvisioningPlan localJRECheckPlan; |
| |
| 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; |
| } |
| |
| @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; |
| } |
| |
| @Override |
| public IWizardPage getPreviousPage(IWizardPage page) { |
| if (page == errorPage) { |
| return mainPage; |
| } |
| return super.getPreviousPage(page); |
| } |
| |
| @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, monitor -> { |
| remediationOperation.setCurrentRemedy(remediationPage.getRemediationGroup().getCurrentRemedy()); |
| remediationOperation.resolveModal(monitor); |
| if (getPolicy().getCheckAgainstCurrentExecutionEnvironment()) { |
| this.localJRECheckPlan = ProvUI |
| .toCompabilityWithCurrentJREProvisioningPlan(remediationOperation, monitor); |
| if (!compatibleWithCurrentEE()) { |
| couldNotResolveStatus = localJRECheckPlan.getStatus(); |
| } |
| } |
| }); |
| } 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(); |
| if (getPolicy().getCheckAgainstCurrentExecutionEnvironment() && !compatibleWithCurrentEE()) { |
| return errorPage; |
| } |
| 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 (getPolicy().getCheckAgainstCurrentExecutionEnvironment() && !compatibleWithCurrentEE()) { |
| // skip remediation for EE compatibility issues |
| return errorPage; |
| } |
| 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); |
| } |
| |
| private boolean compatibleWithCurrentEE() { |
| if (operation == null || !getPolicy().getCheckAgainstCurrentExecutionEnvironment()) { |
| return true; |
| } |
| if (localJRECheckPlan == null) { |
| try { |
| getContainer().run(true, true, monitor -> { |
| if (!operation.hasResolved()) { |
| operation.resolveModal(monitor); |
| } |
| if (operation.hasResolved()) { |
| this.localJRECheckPlan = ProvUI.toCompabilityWithCurrentJREProvisioningPlan(operation, null); |
| if (!compatibleWithCurrentEE()) { |
| couldNotResolveStatus = localJRECheckPlan.getStatus(); |
| } |
| } |
| }); |
| } catch (InvocationTargetException | InterruptedException e) { |
| return false; |
| } |
| } |
| IStatus currentEEPlanStatus = localJRECheckPlan.getStatus(); |
| if (currentEEPlanStatus.getSeverity() != IStatus.ERROR) { |
| return true; |
| } |
| return Stream.of(currentEEPlanStatus).filter(status -> status.getSeverity() == IStatus.ERROR) |
| .flatMap(status -> status.isMultiStatus() ? Stream.of(status.getChildren()) : Stream.of(status)) |
| .filter(status -> status.getSeverity() == IStatus.ERROR) |
| .flatMap(status -> status.isMultiStatus() ? Stream.of(status.getChildren()) : Stream.of(status)) |
| .filter(status -> status.getSeverity() == IStatus.ERROR) |
| .flatMap(status -> status.isMultiStatus() ? Stream.of(status.getChildren()) : Stream.of(status)) |
| .map(IStatus::getMessage).noneMatch(message -> message.contains("osgi.ee")); //$NON-NLS-1$ |
| } |
| |
| /** |
| * 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; |
| localJRECheckPlan = 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, monitor -> { |
| operation.resolveModal(monitor); |
| if (getPolicy().getCheckAgainstCurrentExecutionEnvironment()) { |
| this.localJRECheckPlan = ProvUI.toCompabilityWithCurrentJREProvisioningPlan(operation, monitor); |
| if (!compatibleWithCurrentEE()) { |
| couldNotResolveStatus = localJRECheckPlan.getStatus(); |
| } |
| } |
| 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) { |
| return status == null || status.getSeverity() == IStatus.ERROR; |
| } |
| |
| /* |
| * 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) { |
| if (!operation.getResolutionResult().isOK() || localJRECheckPlan == null || compatibleWithCurrentEE()) { |
| return operation.getResolutionResult(); |
| } else if (!compatibleWithCurrentEE()) { |
| return localJRECheckPlan.getStatus(); |
| } |
| } |
| return couldNotResolveStatus; |
| } |
| |
| public String getDialogSettingsSectionName() { |
| return getClass().getName() + "." + WIZARD_SETTINGS_SECTION; //$NON-NLS-1$ |
| } |
| |
| public void saveBoundsRelatedSettings() { |
| IWizardPage[] pages = getPages(); |
| for (IWizardPage page : pages) { |
| if (page instanceof ProvisioningWizardPage) |
| ((ProvisioningWizardPage) page).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). |
| */ |
| @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(); |
| } |
| |
| }); |
| } |
| |
| } |
| } |
| } |
| |
| @Override |
| public void dispose() { |
| ui.signalRepositoryOperationComplete(null, false); |
| super.dispose(); |
| } |
| |
| void asyncReportLoadFailures() { |
| if (repoPreloadJob != null && getShell() != null && !getShell().isDisposed()) { |
| getShell().getDisplay().asyncExec(() -> { |
| 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 operation != null && operation.getResolutionResult() != null |
| && operation.getResolutionResult().getSeverity() < IStatus.ERROR |
| && (localJRECheckPlan != null && !compatibleWithCurrentEE()); |
| } |
| |
| public void setRelaxedResolution(boolean value) { |
| this.resolveWithRelaxedConstraints = value; |
| } |
| |
| public boolean getRelaxedResoltion() { |
| return this.resolveWithRelaxedConstraints; |
| } |
| } |