/*******************************************************************************
* Copyright (c) 2011-2015 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is 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:
* Red Hat, Inc. - initial API and implementation
******************************************************************************/
package org.jboss.tools.openshift.internal.common.ui.connection;
import java.lang.reflect.InvocationTargetException;
import org.eclipse.core.databinding.Binding;
import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.core.databinding.beans.BeanProperties;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.core.databinding.observable.value.IValueChangeListener;
import org.eclipse.core.databinding.observable.value.ValueChangeEvent;
import org.eclipse.core.databinding.validation.MultiValidator;
import org.eclipse.core.databinding.validation.ValidationStatus;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.equinox.security.storage.StorageException;
import org.eclipse.jface.databinding.fieldassist.ControlDecorationSupport;
import org.eclipse.jface.databinding.swt.WidgetProperties;
import org.eclipse.jface.databinding.viewers.IViewerObservableValue;
import org.eclipse.jface.databinding.viewers.ObservableListContentProvider;
import org.eclipse.jface.databinding.viewers.ViewerProperties;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.dialogs.PageChangingEvent;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.ColumnLabelProvider;
import org.eclipse.jface.viewers.ComboViewer;
import org.eclipse.jface.wizard.IWizard;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.FocusAdapter;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.jboss.tools.common.ui.JobUtils;
import org.jboss.tools.common.ui.WizardUtils;
import org.jboss.tools.common.ui.databinding.InvertingBooleanConverter;
import org.jboss.tools.common.ui.databinding.ParametrizableWizardPageSupport;
import org.jboss.tools.common.ui.databinding.ValueBindingBuilder;
import org.jboss.tools.foundation.core.jobs.DelegatingProgressMonitor;
import org.jboss.tools.foundation.ui.util.BrowserUtility;
import org.jboss.tools.openshift.common.core.connection.ConnectionsRegistrySingleton;
import org.jboss.tools.openshift.common.core.connection.IConnection;
import org.jboss.tools.openshift.common.core.connection.IConnectionFactory;
import org.jboss.tools.openshift.common.core.utils.StringUtils;
import org.jboss.tools.openshift.common.core.utils.UrlUtils;
import org.jboss.tools.openshift.egit.ui.util.EGitUIUtils;
import org.jboss.tools.openshift.internal.common.core.job.AbstractDelegatingMonitorJob;
import org.jboss.tools.openshift.internal.common.core.security.SecureStoreException;
import org.jboss.tools.openshift.internal.common.ui.OpenShiftCommonUIActivator;
import org.jboss.tools.openshift.internal.common.ui.databinding.IsNotNullValidator;
import org.jboss.tools.openshift.internal.common.ui.databinding.RequiredControlDecorationUpdater;
import org.jboss.tools.openshift.internal.common.ui.databinding.TrimTrailingSlashConverter;
import org.jboss.tools.openshift.internal.common.ui.utils.StyledTextUtils;
import org.jboss.tools.openshift.internal.common.ui.utils.UIUtils;
import org.jboss.tools.openshift.internal.common.ui.wizard.AbstractOpenShiftWizardPage;
import org.jboss.tools.openshift.internal.common.ui.wizard.IConnectionAware;
/**
* @author Andre Dietisheim
* @author Xavier Coulon
* @contributor Nick Boldt
*/
public class ConnectionWizardPage extends AbstractOpenShiftWizardPage {
private final ConnectionWizardPageModel pageModel;
private ConnectionEditorsStackedView connectionEditors;
private AdvancedConnectionEditorsStackedView advConnectionEditors;
private StyledText userdocLink;
public <C extends IConnection> ConnectionWizardPage(IWizard wizard, IConnectionAware<C> wizardModel) {
this(wizard, wizardModel, true);
}
public <C extends IConnection> ConnectionWizardPage(IWizard wizard, IConnectionAware<C> wizardModel, Class<? extends IConnection> connectionType) {
this(wizard, wizardModel, connectionType, true);
}
protected <C extends IConnection> ConnectionWizardPage(IWizard wizard, IConnectionAware<C> wizardModel, boolean allowConnectionChange) {
this(wizard, wizardModel, null, allowConnectionChange);
}
@SuppressWarnings("unchecked")
protected <C extends IConnection> ConnectionWizardPage(IWizard wizard, IConnectionAware<C> wizardModel, Class<? extends IConnection> connectionType,
boolean allowConnectionChange) {
super("Sign in to OpenShift", "Please sign in to your OpenShift server.", "Server Connection", wizard);
this.pageModel = new ConnectionWizardPageModel(
wizardModel.getConnection(),
ConnectionsRegistrySingleton.getInstance().getAll(),
connectionType,
allowConnectionChange,
(IConnectionAware<IConnection>) wizardModel);
/*
* JBIDE-12999: ensure EclipseAuthenticator is installed and overrides
* NetAuthenticator
*/
EGitUIUtils.ensureEgitUIIsStarted();
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
protected void doCreateControls(final Composite parent, DataBindingContext dbc) {
GridLayoutFactory.fillDefaults().numColumns(3).margins(10, 10).applyTo(parent);
// userdoc link (JBIDE-20401)
this.userdocLink = new StyledText(parent, SWT.WRAP); // text set in #showHideUserdocLink
GridDataFactory.fillDefaults()
.align(SWT.LEFT, SWT.CENTER).span(3, 1).applyTo(userdocLink);
showHideUserdocLink();
IObservableValue userdocUrlObservable = BeanProperties
.value(ConnectionWizardPageModel.PROPERTY_USERDOCURL).observe(pageModel);
StyledTextUtils.emulateLinkAction(userdocLink, r->onUserdocLinkClicked(userdocUrlObservable));
userdocUrlObservable.addValueChangeListener(new IValueChangeListener() {
@Override
public void handleValueChange(ValueChangeEvent event) {
showHideUserdocLink();
}
});
IObservableValue connectionFactoryObservable =
BeanProperties.value(ConnectionWizardPageModel.PROPERTY_CONNECTION_FACTORY).observe(pageModel);
// filler
Label fillerLabel = new Label(parent, SWT.NONE);
GridDataFactory.fillDefaults()
.span(3, 3).hint(SWT.DEFAULT, 6).applyTo(fillerLabel);
// existing connections combo
Label connectionLabel = new Label(parent, SWT.NONE);
connectionLabel.setText("Connection:");
GridDataFactory.fillDefaults()
.align(SWT.LEFT, SWT.CENTER).hint(100, SWT.DEFAULT).applyTo(connectionLabel);
Combo connectionCombo = new Combo(parent, SWT.BORDER | SWT.READ_ONLY);
GridDataFactory.fillDefaults()
.span(2,1).align(SWT.FILL, SWT.CENTER).grab(true, false).applyTo(connectionCombo);
ComboViewer connectionComboViewer = new ComboViewer(connectionCombo);
connectionComboViewer.setContentProvider(ArrayContentProvider.getInstance());
connectionComboViewer.setLabelProvider(new ConnectionColumLabelProvider());
connectionComboViewer.setInput(pageModel.getAllConnections());
Binding selectedConnectionBinding = ValueBindingBuilder
.bind(ViewerProperties.singleSelection().observe(connectionComboViewer))
.validatingAfterGet(
new IsNotNullValidator(
ValidationStatus.cancel("You have to select or create a new connection.")))
.to(BeanProperties.value(
ConnectionWizardPageModel.PROPERTY_SELECTED_CONNECTION, IConnection.class)
.observe(pageModel))
.in(dbc);
ControlDecorationSupport
.create(selectedConnectionBinding, SWT.LEFT | SWT.TOP, null, new RequiredControlDecorationUpdater());
// server type
Label connectionFactoryLabel = new Label(parent, SWT.NONE);
connectionFactoryLabel.setText("Server type:");
GridDataFactory.fillDefaults()
.align(SWT.LEFT, SWT.CENTER).hint(100, SWT.DEFAULT).applyTo(connectionFactoryLabel);
Combo connectionFactoryCombo = new Combo(parent, SWT.BORDER | SWT.READ_ONLY);
GridDataFactory.fillDefaults()
.span(2,1).align(SWT.FILL, SWT.CENTER).grab(true, false).applyTo(connectionFactoryCombo);
ComboViewer connectionFactoriesViewer = new ComboViewer(connectionFactoryCombo);
connectionFactoriesViewer.setContentProvider(ArrayContentProvider.getInstance());
connectionFactoriesViewer.setLabelProvider(new ColumnLabelProvider() {
@Override
public String getText(Object element) {
if (!(element instanceof IConnectionFactory)) {
return element.toString();
} else {
return ((IConnectionFactory) element).getName();
}
}
});
connectionFactoriesViewer.setInput(pageModel.getAllConnectionFactories());
final IViewerObservableValue selectedServerType = ViewerProperties.singleSelection().observe(connectionFactoriesViewer);
ValueBindingBuilder
.bind(selectedServerType)
.to(connectionFactoryObservable)
.in(dbc);
// server
Button useDefaultServerCheckbox = new Button(parent, SWT.CHECK);
useDefaultServerCheckbox.setText("Use default server");
GridDataFactory.fillDefaults()
.span(3,1).align(SWT.FILL, SWT.FILL).applyTo(useDefaultServerCheckbox);
ValueBindingBuilder
.bind(WidgetProperties.selection().observe(useDefaultServerCheckbox))
.to(BeanProperties.value(
ConnectionWizardPageModel.PROPERTY_USE_DEFAULT_HOST, IConnection.class).observe(pageModel))
.in(dbc);
IObservableValue hasDefaultHostObservable =
BeanProperties.value(ConnectionWizardPageModel.PROPERTY_HAS_DEFAULT_HOST).observe(pageModel);
ValueBindingBuilder.bind(WidgetProperties.enabled().observe(useDefaultServerCheckbox))
.notUpdating(hasDefaultHostObservable).in(dbc);
Label serverLabel = new Label(parent, SWT.NONE);
serverLabel.setText("Server:");
GridDataFactory.fillDefaults()
.align(SWT.LEFT, SWT.CENTER).hint(100, SWT.DEFAULT).applyTo(serverLabel);
Combo serversCombo = new Combo(parent, SWT.BORDER);
ComboViewer serversViewer = new ComboViewer(serversCombo);
serversViewer.setContentProvider(new ObservableListContentProvider());
serversViewer.setInput(BeanProperties.list(ConnectionWizardPageModel.PROPERTY_ALL_HOSTS).observe(pageModel));
GridDataFactory.fillDefaults()
.align(SWT.FILL, SWT.FILL).grab(true, false).applyTo(serversCombo);
final IObservableValue serverUrlObservable = WidgetProperties.text().observe(serversCombo);
serversCombo.addFocusListener(onServerFocusLost(serverUrlObservable));
ValueBindingBuilder.bind(serverUrlObservable)
.converting(new TrimTrailingSlashConverter())
.to(BeanProperties.value(ConnectionWizardPageModel.PROPERTY_HOST).observe(pageModel))
.in(dbc);
MultiValidator serverUrlValidator = new MultiValidator() {
@Override
protected IStatus validate() {
Object value = serverUrlObservable.getValue();
if (!(value instanceof String)
|| StringUtils.isEmpty((String) value)) {
return ValidationStatus.cancel("Please provide an OpenShift server url.");
} else if (!UrlUtils.isValid((String) value)) {
return ValidationStatus.error("Please provide a valid OpenShift server url.");
}
return ValidationStatus.ok();
}
};
ControlDecorationSupport
.create(serverUrlValidator, SWT.LEFT | SWT.TOP, null, new RequiredControlDecorationUpdater());
dbc.addValidationStatusProvider(serverUrlValidator);
ValueBindingBuilder
.bind(WidgetProperties.enabled().observe(serversCombo))
.notUpdatingParticipant()
.to(BeanProperties.value(ConnectionWizardPageModel.PROPERTY_USE_DEFAULT_HOST).observe(pageModel))
.converting(new InvertingBooleanConverter())
.in(dbc);
// connect error
dbc.addValidationStatusProvider(new MultiValidator() {
IObservableValue observable = BeanProperties
.value(ConnectionWizardPageModel.PROPERTY_CONNECTED_STATUS, IStatus.class)
.observe(pageModel);
@Override
protected IStatus validate() {
return (IStatus) observable.getValue();
}
});
// connection editors
Group authenticationDetailsGroup = new Group(parent, SWT.NONE);
authenticationDetailsGroup.setText("Authentication");
GridDataFactory.fillDefaults()
.align(SWT.FILL, SWT.FILL).span(3,1).applyTo(authenticationDetailsGroup);
GridLayoutFactory.fillDefaults()
.margins(0, 0).applyTo(authenticationDetailsGroup);
// additional nesting required because of https://bugs.eclipse.org/bugs/show_bug.cgi?id=478618
Composite authenticationDetailsContainer = new Composite(authenticationDetailsGroup, SWT.None);
GridDataFactory.fillDefaults()
.align(SWT.FILL, SWT.FILL).grab(true, true).applyTo(authenticationDetailsContainer);
this.connectionEditors = new ConnectionEditorsStackedView(
connectionFactoryObservable
, this
, authenticationDetailsContainer
, dbc);
connectionEditors.createControls();
// adv editors
Composite advEditorContainer = new Composite(parent, SWT.NONE);
GridLayoutFactory.fillDefaults()
.margins(0, 0).applyTo(authenticationDetailsGroup);
GridDataFactory.fillDefaults()
.align(SWT.FILL, SWT.FILL).span(3, 1).grab(true, true).applyTo(advEditorContainer);
this.advConnectionEditors = new AdvancedConnectionEditorsStackedView(
connectionFactoryObservable
, pageModel
, advEditorContainer
, dbc);
advConnectionEditors.createControls();
}
private void showHideUserdocLink() {
boolean signupUrlExists = !StringUtils.isEmpty(pageModel.getUserdocUrl());
if (signupUrlExists) {
IConnectionFactory factory = pageModel.getConnectionFactory();
if (factory != null) {
StyledTextUtils.emulateLinkWidget(factory.getUserDocText(), userdocLink);
}
}
UIUtils.setVisibleAndExclude(signupUrlExists, userdocLink);
}
private FocusAdapter onServerFocusLost(final IObservableValue<String> serverUrlObservable) {
return new FocusAdapter() {
@Override
public void focusLost(FocusEvent e) {
String value = (String) serverUrlObservable.getValue();
if (StringUtils.isEmpty(value)) {
return;
}
String url = value;
if (!url.startsWith(UrlUtils.SCHEME_HTTP)
&& !url.contains(UrlUtils.SCHEME_TERMINATOR)) {
url = UrlUtils.ensureStartsWithScheme(value, UrlUtils.SCHEME_HTTPS);
}
if (!url.endsWith(UrlUtils.SCHEME_SEPARATOR)) {
url = StringUtils.removeTrailingSlashes(url);
}
url = org.apache.commons.lang.StringUtils.removeEnd(url, "/console");
if (!url.equals(value)) {
serverUrlObservable.setValue(url);
}
}
};
}
protected void onUserdocLinkClicked(final IObservableValue<String> userdocUrlObservable) {
String userdocUrl = (String) userdocUrlObservable.getValue();
if (StringUtils.isEmpty(userdocUrl)) {
return;
}
new BrowserUtility().checkedCreateExternalBrowser(
userdocUrl,
OpenShiftCommonUIActivator.PLUGIN_ID,
OpenShiftCommonUIActivator.getDefault().getLog());
}
@Override
protected void onPageActivated(DataBindingContext dbc) {
super.onPageActivated(dbc);
updateSize();
}
@Override
protected void onPageDeactivated(DataBindingContext dbc) {
pageModel.saveRecentConnection();
}
@Override
protected void onPageWillGetDeactivated(Direction direction, PageChangingEvent event, DataBindingContext dbc) {
if (direction == Direction.BACKWARDS) {
return;
}
if (!isConnected()) {
event.doit = connect();
} else {
//Openshift 2 doesn't have advanced properties
if (pageModel.getConnectionAdvancedPropertiesProvider() != null && getConnection() != null) {
// all non-advanced properties are updated while `pageModel.connect()` if they're changed,
// but we don't need to do long `connect()` again to change advanced properties:
// just read them from UI and refresh wizard model
pageModel.getConnectionAdvancedPropertiesProvider().update(getConnection());
}
pageModel.refreshWizardModel();
}
if (!event.doit) {
}
}
public boolean isConnected() {
return getModel().isConnected();
}
public boolean connect() {
try {
ConnectJob connectJob = new ConnectJob();
WizardUtils.runInWizard(
connectJob, new DelegatingProgressMonitor(), getContainer(), getDatabindingContext());
boolean connected = JobUtils.isOk(connectJob.getConnectionStatus());
if (connected) {
boolean result = pageModel.saveConnection();
if(result) {
SecureStoreException e = pageModel.getRecentSecureStoreException();
if(e != null && e.getCause() instanceof StorageException) {
result = false;
Display.getDefault().asyncExec(new Runnable() {
@Override
public void run() {
String message = "Connection is successful, but access to secure storage is denied.\n"
+ "Please change save password/token settings and try again.\n"
+ "Be aware, that if you select 'Cancel' at this point, you will "
+ "be prompted for secure storage at each request for resources.";
MessageDialog.openWarning(getWizard().getContainer().getShell(),
"Warning", message);
}
});
}
}
return result;
} else {
return false;
}
} catch (InterruptedException e) {
OpenShiftCommonUIActivator.log(NLS.bind("Failed to authenticate user on server at {1}", pageModel.getHost()), e);
return false;
} catch (InvocationTargetException e) {
OpenShiftCommonUIActivator.log(NLS.bind("Failed to authenticate user on server at {1}", pageModel.getHost()), e);
return false;
}
}
public IConnection getConnection() {
return pageModel.getConnection();
}
@Override
protected void setupWizardPageSupport(DataBindingContext dbc) {
ParametrizableWizardPageSupport.create(IStatus.ERROR | IStatus.CANCEL, this, dbc);
}
@Override
public void dispose() {
pageModel.dispose();
}
public ConnectionWizardPageModel getModel() {
return pageModel;
}
private class ConnectJob extends AbstractDelegatingMonitorJob {
private IStatus connectionStatus;
private ConnectJob() {
super("Verifying user credentials...");
}
@Override
protected IStatus doRun(IProgressMonitor monitor) {
connectionStatus = pageModel.connect();
monitor.done();
return Status.OK_STATUS;
}
public IStatus getConnectionStatus() {
return connectionStatus;
}
}
}