blob: 47901c65280ab99b169d26518d0a45a7858f97a0 [file] [log] [blame]
/*******************************************************************************
* 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.getProfileChangeRequest() != null) {
this.localJRECheckPlan = ProvUI.toCompabilityWithCurrentJREProvisioningPlan(operation, null);
if (!compatibleWithCurrentEE()) {
couldNotResolveStatus = localJRECheckPlan.getStatus();
}
}
});
} catch (InvocationTargetException | InterruptedException e) {
return false;
}
}
if (localJRECheckPlan == null) {
return true;
}
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 (operation.getProfileChangeRequest() != null
&& 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;
}
}