Java tutorial
/******************************************************************************* * Copyright (c) 2016 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.ui.wizard.connection; import java.io.File; import java.util.HashMap; import java.util.Map; import org.apache.commons.lang.ObjectUtils; import org.apache.commons.lang.StringUtils; import org.eclipse.core.databinding.Binding; import org.eclipse.core.databinding.DataBindingContext; import org.eclipse.core.databinding.ValidationStatusProvider; import org.eclipse.core.databinding.beans.BeanProperties; import org.eclipse.core.databinding.observable.list.IObservableList; import org.eclipse.core.databinding.observable.list.WritableList; 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.observable.value.WritableValue; import org.eclipse.core.databinding.validation.MultiValidator; import org.eclipse.core.databinding.validation.ValidationStatus; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.IJobChangeEvent; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.core.runtime.jobs.JobChangeAdapter; import org.eclipse.jface.databinding.fieldassist.ControlDecorationSupport; import org.eclipse.jface.databinding.swt.WidgetProperties; import org.eclipse.jface.dialogs.ErrorDialog; import org.eclipse.jface.dialogs.IDialogConstants; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.jface.layout.GridLayoutFactory; import org.eclipse.jface.preference.PreferenceDialog; import org.eclipse.osgi.util.NLS; import org.eclipse.swt.SWT; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.FileDialog; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Link; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Text; import org.eclipse.ui.dialogs.PreferencesUtil; import org.jboss.tools.common.databinding.ObservablePojo; import org.jboss.tools.common.ui.databinding.ValueBindingBuilder; import org.jboss.tools.openshift.common.core.connection.IConnection; import org.jboss.tools.openshift.common.core.connection.NewConnectionMarker; import org.jboss.tools.openshift.core.ICommonAttributes; import org.jboss.tools.openshift.core.connection.Connection; import org.jboss.tools.openshift.core.connection.ConnectionFactory; import org.jboss.tools.openshift.core.connection.registry.RegistryProviderModel; import org.jboss.tools.openshift.core.preferences.IOpenShiftCoreConstants; import org.jboss.tools.openshift.core.preferences.OpenShiftCorePreferences; import org.jboss.tools.openshift.internal.common.ui.connection.ConnectionWizardPageModel; import org.jboss.tools.openshift.internal.common.ui.connection.ConnectionWizardPageModel.IConnectionAdvancedPropertiesProvider; import org.jboss.tools.openshift.internal.common.ui.connection.IAdvancedConnectionPropertiesEditor; 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.databinding.TrimmingStringConverter; import org.jboss.tools.openshift.internal.common.ui.detailviews.BaseDetailsView; import org.jboss.tools.openshift.internal.common.ui.utils.DataBindingUtils; import org.jboss.tools.openshift.internal.common.ui.utils.DialogAdvancedPart; import org.jboss.tools.openshift.internal.common.ui.utils.UIUtils; import org.jboss.tools.openshift.internal.core.preferences.OCBinaryVersionValidator; import org.jboss.tools.openshift.internal.ui.validator.URLValidator; import org.osgi.framework.Version; @SuppressWarnings("rawtypes") public class AdvancedConnectionEditor extends BaseDetailsView implements IAdvancedConnectionPropertiesEditor { private static final String VALIDATOR_URL_TYPE = "image registry"; private static final int DELAY = 500; private AdvancedConnectionEditorModel model; private ConnectionWizardPageModel pageModel; private IObservableValue selectedConnection; private IValueChangeListener connectionChangedListener; private IObservableValue registryURLObservable; private IObservableValue clusterNamespaceObservable; private IConnectionAdvancedPropertiesProvider connectionAdvancedPropertiesProvider; Map<String, Object> extendedProperties = null; private IObservableValue<IStatus> ocLocationValidity = new WritableValue(Status.OK_STATUS, IStatus.class); private IObservableValue<IStatus> ocVersionValidity = new WritableValue(Status.OK_STATUS, IStatus.class); @SuppressWarnings("unchecked") @Override public Composite createControls(Composite parent, Object context, DataBindingContext dbc) { this.pageModel = (ConnectionWizardPageModel) context; this.selectedConnection = BeanProperties.value(ConnectionWizardPageModel.PROPERTY_SELECTED_CONNECTION) .observe(pageModel); this.model = new AdvancedConnectionEditorModel(); Composite composite = setControl(new Composite(parent, SWT.None)); GridLayoutFactory.fillDefaults().applyTo(composite); DialogAdvancedPart part = new DialogAdvancedPart() { @Override protected void createAdvancedContent(Composite advancedComposite) { Label lblRegistry = new Label(advancedComposite, SWT.NONE); lblRegistry.setText("Image Registry URL:"); GridDataFactory.fillDefaults().align(SWT.LEFT, SWT.CENTER).applyTo(lblRegistry); Text txtRegistry = new Text(advancedComposite, SWT.BORDER); GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).grab(true, false).span(1, 1) .applyTo(txtRegistry); Button registryDiscover = new Button(advancedComposite, SWT.PUSH); registryDiscover.setText("Discover..."); registryDiscover.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { discoverRegistryPressed(registryDiscover.getShell()); } }); GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).applyTo(registryDiscover); UIUtils.setDefaultButtonWidth(registryDiscover); registryURLObservable = WidgetProperties.text(SWT.Modify).observeDelayed(DELAY, txtRegistry); Binding registryURLBinding = ValueBindingBuilder.bind(registryURLObservable) .validatingAfterConvert(new URLValidator(VALIDATOR_URL_TYPE, true)) .converting(new TrimTrailingSlashConverter()) .to(BeanProperties.value(AdvancedConnectionEditorModel.PROP_REGISTRY_URL).observe(model)) .in(dbc); ControlDecorationSupport.create(registryURLBinding, SWT.LEFT | SWT.TOP); Label lblNamespace = new Label(advancedComposite, SWT.NONE); lblNamespace.setText("Cluster namespace:"); GridDataFactory.fillDefaults().align(SWT.LEFT, SWT.CENTER).applyTo(lblNamespace); Text txtClusterNamespace = new Text(advancedComposite, SWT.BORDER); GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).grab(true, false).span(2, 1) .applyTo(txtClusterNamespace); clusterNamespaceObservable = WidgetProperties.text(SWT.Modify).observeDelayed(DELAY, txtClusterNamespace); ValueBindingBuilder.bind(clusterNamespaceObservable).converting(new TrimmingStringConverter()).to( BeanProperties.value(AdvancedConnectionEditorModel.PROP_CLUSTER_NAMESPACE).observe(model)) .in(dbc); Link ocWorkspace = new Link(advancedComposite, SWT.PUSH); ocWorkspace.setText("<a>Workspace OC Settings</a>"); GridDataFactory.fillDefaults().align(SWT.LEFT, SWT.CENTER).span(3, 1).applyTo(ocWorkspace); // Override OC location widgets Button overrideOC = new Button(advancedComposite, SWT.CHECK); overrideOC.setText("Override 'oc' location: "); GridDataFactory.fillDefaults().align(SWT.LEFT, SWT.CENTER).applyTo(lblRegistry); IObservableValue<Boolean> overrideOCObservable = WidgetProperties.selection().observe(overrideOC); ValueBindingBuilder.bind(overrideOCObservable) .to(BeanProperties.value(AdvancedConnectionEditorModel.PROP_OC_OVERRIDE).observe(model)) .in(dbc); final Text ocText = new Text(advancedComposite, SWT.SINGLE | SWT.BORDER); GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).grab(true, false).applyTo(ocText); IObservableValue<String> ocLocationObservable = WidgetProperties.text(SWT.Modify).observe(ocText); Binding ocLocationBinding = ValueBindingBuilder.bind(ocLocationObservable) .converting(new TrimmingStringConverter()).to(BeanProperties .value(AdvancedConnectionEditorModel.PROP_OC_OVERRIDE_LOCATION).observe(model)) .in(dbc); overrideOCObservable.addValueChangeListener(new IValueChangeListener<Boolean>() { @Override public void handleValueChange(ValueChangeEvent<? extends Boolean> event) { updateOcObservables(); } }); ocLocationBinding.getValidationStatus().addValueChangeListener(new IValueChangeListener<IStatus>() { @Override public void handleValueChange(ValueChangeEvent<? extends IStatus> event) { updateOcObservables(); } }); ValueBindingBuilder.bind(overrideOCObservable).to(WidgetProperties.enabled().observe(ocText)) .notUpdatingParticipant().in(dbc); Button ocBrowse = new Button(advancedComposite, SWT.PUSH); ocBrowse.setText("Browse..."); GridDataFactory.fillDefaults().align(SWT.LEFT, SWT.CENTER).applyTo(ocBrowse); UIUtils.setDefaultButtonWidth(ocBrowse); ocWorkspace.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { PreferenceDialog pd = PreferencesUtil.createPreferenceDialogOn(null, IOpenShiftCoreConstants.OPEN_SHIFT_PREFERENCE_PAGE_ID, new String[] {}, null); pd.open(); if (!overrideOC.getSelection()) { String ocLoc = OpenShiftCorePreferences.INSTANCE.getOCBinaryLocation(); String nullsafe = ocLoc == null ? "" : ocLoc; ocText.setText(nullsafe); } updateOcObservables(); } }); // Validation here is done via a listener rather than dbc validators // because dbc validators will validate in the UI thread, but validation // of this field requires a background job. ocBrowse.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { FileDialog fd = new FileDialog(ocBrowse.getShell()); fd.setText(ocText.getText()); IPath p = new Path(ocText.getText()); if (p.segmentCount() > 1) { fd.setFilterPath(p.removeLastSegments(1).toOSString()); } String result = fd.open(); if (result != null) { ocLocationObservable.setValue(result); } } }); ValueBindingBuilder.bind(overrideOCObservable).to(WidgetProperties.enabled().observe(ocBrowse)) .notUpdatingParticipant().in(dbc); ValidationStatusProvider ocValidator = new MultiValidator() { public IObservableList getTargets() { WritableList targets = new WritableList(); targets.add(ocLocationObservable); targets.add(ocLocationValidity); targets.add(ocVersionValidity); return targets; } @Override protected IStatus validate() { // access all observables that the framework should listen to IStatus ocLocationStatus = ocLocationValidity.getValue(); IStatus ocVersionStatus = ocVersionValidity.getValue(); IStatus status; if (!ocLocationStatus.isOK()) { status = ocLocationStatus; } else { status = ocVersionStatus; } return status; } }; dbc.addValidationStatusProvider(ocValidator); ControlDecorationSupport.create(ocValidator, SWT.LEFT | SWT.TOP, null, new RequiredControlDecorationUpdater()); } protected GridLayoutFactory adjustAdvancedCompositeLayout(GridLayoutFactory gridLayoutFactory) { return gridLayoutFactory.numColumns(3); } }; part.createAdvancedGroup(composite, 1); this.connectionAdvancedPropertiesProvider = new ConnectionAdvancedPropertiesProvider(); updateOcObservables(); return composite; } private void updateOcObservables() { boolean override = model.getOcOverride(); String location; if (override) { location = model.getOcOverrideLocation(); } else { location = OpenShiftCorePreferences.INSTANCE.getOCBinaryLocation(); } updateOcObservables(location, override); } private void updateOcObservables(String location, boolean override) { IStatus ocLocationStatus = validateOCLocation(location, override); ocLocationValidity.setValue(ocLocationStatus); if (ocLocationStatus.isOK()) { ocVersionValidity.setValue(ValidationStatus.cancel("Verifying oc version...")); OCVersionValidationJob job = new OCVersionValidationJob(model.getOcOverrideLocation()); job.addJobChangeListener(new JobChangeAdapter() { @Override public void done(IJobChangeEvent event) { ocVersionValidity.getRealm().exec(() -> ocVersionValidity.setValue(job.getOCVersionValidity())); } }); job.schedule(); } } private void discoverRegistryPressed(Shell shell) { IConnection tmp = pageModel.createConnection(); IStatus ret = RegistryProviderModel.getDefault().getRegistryURL(tmp); String oldVal = (String) registryURLObservable.getValue(); String newVal = ret.getMessage(); if (ret != null && ret.isOK()) { // If they're equal, do nothing if (!eq(oldVal, newVal)) { // Verify with user String title = "Overwrite registry URL?"; String msg = "Are you sure you want to change the registry URL from " + oldVal + " to " + newVal + "?"; MessageDialog dialog = new MessageDialog(shell, title, null, msg, MessageDialog.CONFIRM, new String[] { "OK", "Cancel" }, 0); String old = registryURLObservable.getValue().toString().trim(); if (old.isEmpty() || dialog.open() == IDialogConstants.OK_ID) { registryURLObservable.setValue(ret.getMessage()); } } } else { String title = "Registry URL not found"; String msg = "No registry provider found for the given connection. If your Openshift connection is backed by a CDK or minishift installation, please ensure the CDK is running."; ErrorDialog ed = new ErrorDialog(shell, title, msg, ret, IStatus.ERROR | IStatus.WARNING | IStatus.INFO | IStatus.CANCEL); ed.open(); } } private boolean eq(String s1, String s2) { if (s1 == null && s2 == null) return true; if (s1 == null) return false; return s1.equals(s2); } private IStatus validateOCLocation(String location, boolean override) { IStatus validity = ValidationStatus.ok(); if (StringUtils.isBlank(location)) { if (override) validity = ValidationStatus.error("Please provide a location for the OC binary."); else { validity = ValidationStatus.error( "The workspace setting for the OC binary is not set. Please update the workspace setting or override the location here in the 'Advanced' section."); } } else { File file = new File(location); // Error messages have to be set to field editor, not directly to // the page. if (override) { if (!file.exists()) { validity = ValidationStatus.error((NLS.bind("{0} was not found.", file))); } else if (file.isDirectory()) { validity = ValidationStatus.error((NLS.bind("OC Location must be a file.", file))); } else if (!file.canExecute()) { validity = ValidationStatus.error(NLS.bind("{0} does not have execute permissions.", file)); } } else { if (!file.exists()) { validity = ValidationStatus .error((NLS.bind("Workspace setting for OC binary location was not found: {0}", file))); } else if (file.isDirectory()) { validity = ValidationStatus .error((NLS.bind("Workspace setting for OC binary location is not a file: {0}", file))); } else if (!file.canExecute()) { validity = ValidationStatus.error(NLS.bind( "Workspace setting for OC binary location does not have execute permissions: {0}", file)); } } } return validity; } private class OCVersionValidationJob extends Job { private Version version; private String location; private IStatus ocVersionValidity = ValidationStatus.cancel("OC version not verified yet."); public OCVersionValidationJob(String location) { super("Checking oc binary..."); this.location = location; } @Override protected IStatus run(IProgressMonitor monitor) { this.version = new OCBinaryVersionValidator(location).getVersion(monitor); if (Version.emptyVersion.equals(version)) { this.ocVersionValidity = ValidationStatus .error("Could not determine your OpenShift client version"); } else if (!OCBinaryVersionValidator.isCompatibleForPublishing(version)) { this.ocVersionValidity = ValidationStatus.error(NLS.bind( "OpenShift client version 1.1.1 or higher is required to avoid rsync issues.", version)); } else { this.ocVersionValidity = ValidationStatus.ok(); } if (monitor.isCanceled()) { this.ocVersionValidity = ValidationStatus.cancel("OC version verification was cancelled."); } return Status.OK_STATUS; } public IStatus getOCVersionValidity() { return ocVersionValidity; } } @SuppressWarnings("unchecked") protected void onSelectedConnectionChanged(IObservableValue selectedConnection) { registryURLObservable.setValue(model.getRegistryURL()); } @Override public void onVisible(IObservableValue detailViewModel, DataBindingContext dbc) { pageModel.setConnectionAdvancedPropertiesProvider(this.connectionAdvancedPropertiesProvider); this.connectionChangedListener = addSelectedConnectionChangedListener(selectedConnection, dbc); } @Override public void onInVisible(IObservableValue detailViewModel, DataBindingContext dbc) { pageModel.setConnectionAdvancedPropertiesProvider(null); removeConnectionChangedListener(connectionChangedListener, selectedConnection); this.connectionChangedListener = null; } @SuppressWarnings("unchecked") private IValueChangeListener addSelectedConnectionChangedListener(final IObservableValue selectedConnection, final DataBindingContext dbc) { IValueChangeListener listener = new IValueChangeListener() { @Override public void handleValueChange(ValueChangeEvent event) { onSelectedConnectionChanged(selectedConnection); DataBindingUtils.validateTargetsToModels(dbc); } }; selectedConnection.addValueChangeListener(listener); return listener; } @SuppressWarnings("unchecked") private void removeConnectionChangedListener(final IValueChangeListener connectionChangedListener, final IObservableValue selectedConnection) { if (selectedConnection == null || selectedConnection.isDisposed() || connectionChangedListener == null) { return; } selectedConnection.removeValueChangeListener(connectionChangedListener); } @Override public boolean isViewFor(Object object) { return object instanceof ConnectionFactory; } public Map<String, Object> getExtendedProperties() { if (extendedProperties != null) { return extendedProperties; } IConnection connection = pageModel.getSelectedConnection(); if (connection != null) { if (NewConnectionMarker.getInstance() == connection) { extendedProperties = new HashMap<String, Object>(); } else if (connection instanceof Connection) { extendedProperties = ((Connection) connection).getExtendedProperties(); } return extendedProperties; } return null; } class AdvancedConnectionEditorModel extends ObservablePojo { static final String PROP_REGISTRY_URL = "registryURL"; static final String PROP_CLUSTER_NAMESPACE = "clusterNamespace"; // custom oc location static final String PROP_OC_OVERRIDE = "ocOverride"; static final String PROP_OC_OVERRIDE_LOCATION = "ocOverrideLocation"; public void setOcOverride(boolean value) { Map<String, Object> properties = getExtendedProperties(); if (properties != null) { Object old = properties.get(ICommonAttributes.OC_OVERRIDE_KEY); properties.put(ICommonAttributes.OC_OVERRIDE_KEY, value); firePropertyChange(PROP_OC_OVERRIDE, old, value); } } public boolean getOcOverride() { Map<String, Object> properties = getExtendedProperties(); if (properties != null) { return ((Boolean) (ObjectUtils.defaultIfNull(properties.get(ICommonAttributes.OC_OVERRIDE_KEY), false))).booleanValue(); } return false; } public void setOcOverrideLocation(String value) { Map<String, Object> properties = getExtendedProperties(); if (properties != null) { Object old = properties.get(ICommonAttributes.OC_LOCATION_KEY); properties.put(ICommonAttributes.OC_LOCATION_KEY, value); firePropertyChange(PROP_OC_OVERRIDE_LOCATION, old, value); } } public String getOcOverrideLocation() { if (getOcOverride()) { Map<String, Object> properties = getExtendedProperties(); if (properties != null) { return (String) ObjectUtils.defaultIfNull(properties.get(ICommonAttributes.OC_LOCATION_KEY), ""); } } return OpenShiftCorePreferences.INSTANCE.getOCBinaryLocation(); } public void setRegistryURL(String value) { Map<String, Object> properties = getExtendedProperties(); if (properties != null) { Object old = properties.get(ICommonAttributes.IMAGE_REGISTRY_URL_KEY); properties.put(ICommonAttributes.IMAGE_REGISTRY_URL_KEY, value); firePropertyChange(PROP_REGISTRY_URL, old, value); } } public String getRegistryURL() { Map<String, Object> properties = getExtendedProperties(); if (properties != null) { return (String) ObjectUtils.defaultIfNull(properties.get(ICommonAttributes.IMAGE_REGISTRY_URL_KEY), ""); } return ""; } public void setClusterNamespace(String value) { Map<String, Object> properties = getExtendedProperties(); if (properties != null) { Object old = properties.get(ICommonAttributes.CLUSTER_NAMESPACE_KEY); properties.put(ICommonAttributes.CLUSTER_NAMESPACE_KEY, value); firePropertyChange(PROP_CLUSTER_NAMESPACE, old, value); } } public String getClusterNamespace() { Map<String, Object> properties = getExtendedProperties(); if (properties != null) { return (String) properties.getOrDefault(ICommonAttributes.CLUSTER_NAMESPACE_KEY, ICommonAttributes.COMMON_NAMESPACE); } return ICommonAttributes.COMMON_NAMESPACE; } } private class ConnectionAdvancedPropertiesProvider implements IConnectionAdvancedPropertiesProvider { @Override public IConnection update(IConnection conn) { Assert.isLegal(conn instanceof Connection); final Connection connection = (Connection) conn; Display.getDefault().syncExec(new Runnable() { @Override public void run() { connection.setExtendedProperty(ICommonAttributes.IMAGE_REGISTRY_URL_KEY, registryURLObservable.getValue()); connection.setExtendedProperty(ICommonAttributes.CLUSTER_NAMESPACE_KEY, clusterNamespaceObservable.getValue()); } }); return connection; } } @Override public void saveChanges(ConnectionWizardPageModel pageModel) { IConnection c = pageModel.getConnection(); if (c instanceof Connection && getExtendedProperties() != null) { ((Connection) c).setExtendedProperties(getExtendedProperties()); } } }