Java tutorial
/* * Copyright 2016 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.cloud.tools.eclipse.appengine.deploy.ui; import com.google.api.client.auth.oauth2.Credential; import com.google.cloud.tools.eclipse.appengine.login.IGoogleLoginService; import com.google.cloud.tools.eclipse.appengine.login.ui.AccountSelector; import com.google.cloud.tools.eclipse.appengine.login.ui.AccountSelectorObservableValue; import com.google.cloud.tools.eclipse.ui.util.FontUtil; import com.google.cloud.tools.eclipse.ui.util.databinding.BucketNameValidator; import com.google.cloud.tools.eclipse.ui.util.databinding.ProjectIdInputValidator; import com.google.cloud.tools.eclipse.ui.util.databinding.ProjectVersionValidator; import com.google.cloud.tools.eclipse.ui.util.event.OpenUriSelectionListener; import com.google.cloud.tools.eclipse.ui.util.event.OpenUriSelectionListener.ErrorHandler; import com.google.cloud.tools.eclipse.ui.util.event.OpenUriSelectionListener.QueryParameterProvider; import com.google.common.base.Preconditions; import com.google.common.base.Strings; import java.util.Collections; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import org.eclipse.core.databinding.DataBindingContext; import org.eclipse.core.databinding.ObservablesManager; import org.eclipse.core.databinding.UpdateValueStrategy; import org.eclipse.core.databinding.beans.PojoProperties; import org.eclipse.core.databinding.conversion.Converter; import org.eclipse.core.databinding.observable.Observables; import org.eclipse.core.databinding.observable.list.IObservableList; import org.eclipse.core.databinding.observable.value.IObservableValue; import org.eclipse.core.databinding.observable.value.WritableValue; import org.eclipse.core.databinding.validation.IValidator; import org.eclipse.core.databinding.validation.MultiValidator; import org.eclipse.core.databinding.validation.ValidationStatus; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.IStatus; import org.eclipse.jface.databinding.swt.ISWTObservableValue; import org.eclipse.jface.databinding.swt.WidgetProperties; import org.eclipse.jface.dialogs.Dialog; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.jface.layout.GridLayoutFactory; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Link; import org.eclipse.swt.widgets.Text; import org.eclipse.ui.forms.events.ExpansionAdapter; import org.eclipse.ui.forms.events.ExpansionEvent; import org.eclipse.ui.forms.widgets.ExpandableComposite; import org.osgi.service.prefs.BackingStoreException; public class StandardDeployPreferencesPanel extends DeployPreferencesPanel { private static final String APPENGINE_VERSIONS_URL = "https://console.cloud.google.com/appengine/versions"; private static final String URI_PARAM_PROJECT = "project"; private static final int INDENT_CHECKBOX_ENABLED_WIDGET = 10; private static Logger logger = Logger.getLogger(DeployPropertyPage.class.getName()); private AccountSelector accountSelector; private Label projectIdLabel; private Text projectId; private Button overrideDefaultVersionButton; private Label versionLabel; private Text version; private Button autoPromoteButton; private Button stopPreviousVersionButton; private Button overrideDefaultBucketButton; private Label bucketLabel; private Text bucket; private ExpandableComposite expandableComposite; private DeployPreferencesModel model; private ObservablesManager observables; private DataBindingContext bindingContext; private Runnable layoutChangedHandler; private boolean requireValues = true; public StandardDeployPreferencesPanel(Composite parent, IProject project, IGoogleLoginService loginService, Runnable layoutChangedHandler, boolean requireValues) { super(parent, SWT.NONE); this.layoutChangedHandler = layoutChangedHandler; this.requireValues = requireValues; createCredentialSection(loginService); createProjectIdSection(); createProjectVersionSection(); createPromoteSection(); createAdvancedSection(); Dialog.applyDialogFont(this); GridLayoutFactory.fillDefaults().spacing(0, 0).generateLayout(this); loadPreferences(project); setupDataBinding(); } private void setupDataBinding() { bindingContext = new DataBindingContext(); setupAccountEmailDataBinding(bindingContext); setupProjectIdDataBinding(bindingContext); setupProjectVersionDataBinding(bindingContext); setupAutoPromoteDataBinding(bindingContext); setupBucketDataBinding(bindingContext); observables = new ObservablesManager(); observables.addObservablesFromContext(bindingContext, true, true); } private void setupAccountEmailDataBinding(DataBindingContext context) { IValidator accountSelectedChecker = new IValidator() { @Override public IStatus validate(Object value /* email */) { if (requireValues && Strings.isNullOrEmpty((String) value)) { if (accountSelector.isSignedIn()) { return ValidationStatus.error(Messages.getString("error.account.missing.signedin")); } else { return ValidationStatus.error(Messages.getString("error.account.missing.signedout")); } } return ValidationStatus.ok(); } }; UpdateValueStrategy targetToModel = new UpdateValueStrategy().setBeforeSetValidator(accountSelectedChecker); UpdateValueStrategy modelToTarget = new UpdateValueStrategy().setBeforeSetValidator(accountSelectedChecker) .setConverter(new Converter(String.class, String.class) { @Override public Object convert(Object fromObject /* email */) { return accountSelector.isEmailAvailable((String) fromObject) ? fromObject : null; } }); final IObservableValue accountEmailModel = PojoProperties.value("accountEmail").observe(model); context.bindValue(new AccountSelectorObservableValue(accountSelector), accountEmailModel, targetToModel, modelToTarget); } private void setupProjectIdDataBinding(DataBindingContext context) { ISWTObservableValue projectIdField = WidgetProperties.text(SWT.Modify).observe(projectId); IObservableValue projectIdModel = PojoProperties.value("projectId").observe(model); context.bindValue(projectIdField, projectIdModel, new UpdateValueStrategy().setAfterGetValidator(new ProjectIdInputValidator(requireValues)), new UpdateValueStrategy().setAfterGetValidator(new ProjectIdInputValidator(requireValues))); } private void setupProjectVersionDataBinding(DataBindingContext context) { ISWTObservableValue overrideButton = WidgetProperties.selection().observe(overrideDefaultVersionButton); ISWTObservableValue versionField = WidgetProperties.text(SWT.Modify).observe(version); ISWTObservableValue versionLabelEnablement = WidgetProperties.enabled().observe(versionLabel); ISWTObservableValue versionFieldEnablement = WidgetProperties.enabled().observe(version); // use an intermediary value to control the enabled state of the label and the field based on the override // checkbox's state WritableValue enablement = new WritableValue(); context.bindValue(overrideButton, enablement); context.bindValue(versionLabelEnablement, enablement); context.bindValue(versionFieldEnablement, enablement); IObservableValue overrideModel = PojoProperties.value("overrideDefaultVersioning").observe(model); IObservableValue versionModel = PojoProperties.value("version").observe(model); context.bindValue(enablement, overrideModel); context.bindValue(versionField, versionModel); context.addValidationStatusProvider( new OverrideValidator(overrideButton, versionField, new ProjectVersionValidator())); } private void setupAutoPromoteDataBinding(DataBindingContext context) { ISWTObservableValue promoteButton = WidgetProperties.selection().observe(autoPromoteButton); ISWTObservableValue stopPreviousVersion = WidgetProperties.selection().observe(stopPreviousVersionButton); ISWTObservableValue stopPreviousVersionEnablement = WidgetProperties.enabled() .observe(stopPreviousVersionButton); // use an intermediary value to control the enabled state of stopPreviousVersionButton based on the promote // checkbox's state WritableValue enablement = new WritableValue(); context.bindValue(promoteButton, enablement); context.bindValue(stopPreviousVersionEnablement, enablement); IObservableValue promoteModel = PojoProperties.value("autoPromote").observe(model); IObservableValue stopPreviousVersionModel = PojoProperties.value("stopPreviousVersion").observe(model); context.bindValue(promoteButton, promoteModel); context.bindValue(stopPreviousVersion, stopPreviousVersionModel); } private void setupBucketDataBinding(DataBindingContext context) { ISWTObservableValue overrideButton = WidgetProperties.selection().observe(overrideDefaultBucketButton); ISWTObservableValue bucketField = WidgetProperties.text(SWT.Modify).observe(bucket); ISWTObservableValue bucketLabelEnablement = WidgetProperties.enabled().observe(bucketLabel); ISWTObservableValue bucketFieldEnablement = WidgetProperties.enabled().observe(bucket); // use an intermediary value to control the enabled state of the label and the field based on the override // checkbox's state WritableValue enablement = new WritableValue(); context.bindValue(overrideButton, enablement); context.bindValue(bucketLabelEnablement, enablement); context.bindValue(bucketFieldEnablement, enablement); IObservableValue overrideModelObservable = PojoProperties.value("overrideDefaultBucket").observe(model); IObservableValue bucketModelObservable = PojoProperties.value("bucket").observe(model); context.bindValue(enablement, overrideModelObservable); context.bindValue(bucketField, bucketModelObservable); context.addValidationStatusProvider( new OverrideValidator(overrideButton, bucketField, new BucketNameValidator())); } @Override public boolean savePreferences() { try { model.savePreferences(); return true; } catch (BackingStoreException exception) { logger.log(Level.SEVERE, "Could not save deploy preferences", exception); MessageDialog.openError(getShell(), Messages.getString("deploy.preferences.save.error.title"), Messages.getString("deploy.preferences.save.error.message", exception.getLocalizedMessage())); return false; } } private void loadPreferences(IProject project) { model = new DeployPreferencesModel(project); } public Credential getSelectedCredential() { return accountSelector.getSelectedCredential(); } private void createCredentialSection(IGoogleLoginService loginService) { Composite accountComposite = new Composite(this, SWT.NONE); new Label(accountComposite, SWT.LEFT) .setText(Messages.getString("deploy.preferences.dialog.label.selectAccount")); accountSelector = new AccountSelector(accountComposite, loginService, Messages.getString("deploy.preferences.dialog.accountSelector.login")); GridLayoutFactory.fillDefaults().numColumns(2).generateLayout(accountComposite); } private void createProjectIdSection() { Composite projectIdComposite = new Composite(this, SWT.NONE); projectIdLabel = new Label(projectIdComposite, SWT.LEFT); projectIdLabel.setText(Messages.getString("project.id")); projectId = new Text(projectIdComposite, SWT.LEFT | SWT.SINGLE | SWT.BORDER); GridLayoutFactory.fillDefaults().numColumns(2).generateLayout(projectIdComposite); } private void createProjectVersionSection() { Composite versionComposite = new Composite(this, SWT.NONE); overrideDefaultVersionButton = new Button(versionComposite, SWT.CHECK); overrideDefaultVersionButton.setText(Messages.getString("use.custom.versioning")); overrideDefaultVersionButton.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false, 2, 1)); versionLabel = new Label(versionComposite, SWT.NONE); versionLabel.setText(Messages.getString("project.version")); GridData layoutData = GridDataFactory.swtDefaults().create(); layoutData.horizontalIndent = INDENT_CHECKBOX_ENABLED_WIDGET; versionLabel.setLayoutData(layoutData); version = new Text(versionComposite, SWT.LEFT | SWT.SINGLE | SWT.BORDER); GridLayoutFactory.fillDefaults().numColumns(2).generateLayout(versionComposite); } private void createPromoteSection() { Composite promoteComposite = new Composite(this, SWT.NONE); autoPromoteButton = new Button(promoteComposite, SWT.CHECK); autoPromoteButton.setText(Messages.getString("auto.promote")); Link manualPromoteLink = new Link(promoteComposite, SWT.NONE); GridData layoutData = GridDataFactory.swtDefaults().create(); layoutData.horizontalIndent = INDENT_CHECKBOX_ENABLED_WIDGET; manualPromoteLink.setLayoutData(layoutData); manualPromoteLink.setText(Messages.getString("deploy.manual.link", APPENGINE_VERSIONS_URL)); manualPromoteLink.setFont(promoteComposite.getFont()); manualPromoteLink.addSelectionListener(new OpenUriSelectionListener(new QueryParameterProvider() { @Override public Map<String, String> getParameters() { return Collections.singletonMap(URI_PARAM_PROJECT, projectId.getText().trim()); } }, new ErrorHandler() { @Override public void handle(Exception ex) { MessageDialog.openError(getShell(), Messages.getString("cannot.open.browser"), ex.getLocalizedMessage()); } })); stopPreviousVersionButton = new Button(promoteComposite, SWT.CHECK); stopPreviousVersionButton.setText(Messages.getString("stop.previous.version")); GridDataFactory.swtDefaults().indent(INDENT_CHECKBOX_ENABLED_WIDGET, 0).applyTo(stopPreviousVersionButton); GridLayoutFactory.fillDefaults().generateLayout(promoteComposite); } private void createAdvancedSection() { createExpandableComposite(); final Composite bucketComposite = createBucketSection(expandableComposite); expandableComposite.setClient(bucketComposite); expandableComposite.addExpansionListener(new ExpansionAdapter() { @Override public void expansionStateChanged(ExpansionEvent e) { handleExpansionStateChanged(); } }); GridLayoutFactory.fillDefaults().generateLayout(expandableComposite); } private void createExpandableComposite() { expandableComposite = new ExpandableComposite(this, SWT.NONE, ExpandableComposite.TWISTIE | ExpandableComposite.CLIENT_INDENT); FontUtil.convertFontToBold(expandableComposite); expandableComposite.setText(Messages.getString("settings.advanced")); expandableComposite.setExpanded(false); GridDataFactory.fillDefaults().applyTo(expandableComposite); getFormToolkit().adapt(expandableComposite, true, true); } private Composite createBucketSection(Composite parent) { Composite bucketComposite = new Composite(parent, SWT.NONE); overrideDefaultBucketButton = new Button(bucketComposite, SWT.CHECK); overrideDefaultBucketButton.setText(Messages.getString("use.custom.bucket")); overrideDefaultBucketButton.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false, 2, 1)); bucketLabel = new Label(bucketComposite, SWT.RADIO); bucketLabel.setText(Messages.getString("bucket.name")); bucket = new Text(bucketComposite, SWT.LEFT | SWT.SINGLE | SWT.BORDER); GridLayoutFactory.fillDefaults().numColumns(2).generateLayout(bucketComposite); return bucketComposite; } /** * Validates a checkbox and text field as follows: * <ol> * <li>if the checkbox is unselected -> valid * <li>if the checkbox is selected -> the result is determined by the provided <code>validator</code> used * on the value of the text field * </ol> * */ private static class OverrideValidator extends FixedMultiValidator { private ISWTObservableValue selectionObservable; private ISWTObservableValue textObservable; private IValidator validator; /** * @param selection must be an observable for a checkbox, i.e. a {@link Button} with {@link SWT#CHECK} style * @param text must be an observable for a {@link Text} * @param validator must be a validator for String values, will be applied to <code>text.getValue()</code> */ public OverrideValidator(ISWTObservableValue selection, ISWTObservableValue text, IValidator validator) { Preconditions.checkArgument(text.getWidget() instanceof Text, "text is an observable for {0}, should be for {1}", text.getWidget().getClass().getName(), Text.class.getName()); Preconditions.checkArgument(selection.getWidget() instanceof Button, "selection is an observable for {0}, should be for {1}", selection.getWidget().getClass().getName(), Button.class.getName()); Preconditions.checkArgument((selection.getWidget().getStyle() & SWT.CHECK) != 0, "selection must be an observable for a checkbox"); this.selectionObservable = selection; this.textObservable = text; this.validator = validator; } @Override protected IStatus validate() { if (Boolean.FALSE.equals(selectionObservable.getValue())) { return ValidationStatus.ok(); } return validator.validate(textObservable.getValue()); } } // BUGFIX: https://bugs.eclipse.org/bugs/show_bug.cgi?id=312785 private abstract static class FixedMultiValidator extends MultiValidator { @Override public IObservableList getTargets() { if (isDisposed()) { return Observables.emptyObservableList(); } return super.getTargets(); } }; @Override public DataBindingContext getDataBindingContext() { return bindingContext; } @Override public void resetToDefaults() { model.resetToDefaults(); bindingContext.updateTargets(); } @Override public void dispose() { if (bindingContext != null) { bindingContext.dispose(); } if (observables != null) { observables.dispose(); } super.dispose(); } private void handleExpansionStateChanged() { if (layoutChangedHandler != null) { layoutChangedHandler.run(); } } @Override public void setFont(Font font) { super.setFont(font); expandableComposite.setFont(font); FontUtil.convertFontToBold(expandableComposite); } }