package com.amazonaws.eclipse.opsworks.deploy.wizard.page; import static com.amazonaws.eclipse.core.ui.wizards.WizardWidgetFactory.newCombo; import static com.amazonaws.eclipse.core.ui.wizards.WizardWidgetFactory.newControlDecoration; import static com.amazonaws.eclipse.core.ui.wizards.WizardWidgetFactory.newFillingLabel; import static com.amazonaws.eclipse.core.ui.wizards.WizardWidgetFactory.newGroup; import static com.amazonaws.eclipse.core.ui.wizards.WizardWidgetFactory.newLink; import static com.amazonaws.eclipse.core.ui.wizards.WizardWidgetFactory.newRadioButton; import static com.amazonaws.eclipse.core.ui.wizards.WizardWidgetFactory.newText; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.eclipse.core.databinding.AggregateValidationStatus; import org.eclipse.core.databinding.Binding; import org.eclipse.core.databinding.DataBindingContext; import org.eclipse.core.databinding.beans.PojoObservables; import org.eclipse.core.databinding.observable.ChangeEvent; import org.eclipse.core.databinding.observable.IChangeListener; import org.eclipse.core.databinding.observable.value.IObservableValue; import org.eclipse.core.databinding.observable.value.WritableValue; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.jface.databinding.swt.ISWTObservableValue; import org.eclipse.jface.databinding.swt.SWTObservables; import org.eclipse.jface.fieldassist.ControlDecoration; 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.layout.GridLayout; 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.Link; import org.eclipse.swt.widgets.Text; import org.eclipse.ui.statushandlers.StatusManager; import com.amazonaws.eclipse.core.AwsToolkitCore; import com.amazonaws.eclipse.core.regions.Region; import com.amazonaws.eclipse.core.regions.RegionUtils; import com.amazonaws.eclipse.core.regions.ServiceAbbreviations; import com.amazonaws.eclipse.core.ui.CancelableThread; import com.amazonaws.eclipse.core.ui.WebLinkListener; import com.amazonaws.eclipse.databinding.BooleanValidator; import com.amazonaws.eclipse.databinding.ChainValidator; import com.amazonaws.eclipse.databinding.DecorationChangeListener; import com.amazonaws.eclipse.databinding.NotEmptyValidator; import com.amazonaws.eclipse.opsworks.OpsWorksPlugin; import com.amazonaws.eclipse.opsworks.ServiceAPIUtils; import com.amazonaws.eclipse.opsworks.UrlConstants; import com.amazonaws.eclipse.opsworks.deploy.wizard.model.DeployProjectToOpsworksWizardDataModel; import com.amazonaws.services.opsworks.AWSOpsWorks; import com.amazonaws.services.opsworks.model.App; import com.amazonaws.services.opsworks.model.Stack; public class TargetAppSelectionPage extends WizardPageWithOnEnterHook { /* Data model and binding */ private final DeployProjectToOpsworksWizardDataModel dataModel; private final DataBindingContext bindingContext; private final AggregateValidationStatus aggregateValidationStatus; /* UI widgets */ // Select region private Combo regionCombo; // Select stack private IObservableValue existingStackLoaded = new WritableValue(); private Combo stackNameCombo; private Link stackSelectionMessageLabel; // Select Java application private IObservableValue existingJavaAppLoaded = new WritableValue(); private Button useExistingJavaAppRadioButton; private ISWTObservableValue useExistingJavaAppRadioButtonObservable; private Combo existingJavaAppNameCombo; private Button createNewJavaAppRadioButton; private ISWTObservableValue createNewJavaAppRadioButtonObservable; private Text newApplicationNameText; private ControlDecoration newApplicationNameDecoration; private ISWTObservableValue newApplicationNameTextObservable; /* Other */ private AWSOpsWorks opsworksClient; private LoadStacksThread loadStacksThread; private LoadJavaAppsThread loadJavaAppsThread; private final WebLinkListener webLinkListener = new WebLinkListener(); protected final SelectionListener javaAppSectionRadioButtonSelectionListener = new SelectionAdapter() { public void widgetSelected(SelectionEvent e) { radioButtonSelected(e.getSource()); runValidators(); } }; /* Constants */ private static final String LOADING = "Loading..."; private static final String NONE_FOUND = "None found"; public TargetAppSelectionPage(DeployProjectToOpsworksWizardDataModel dataModel) { super("Select Target Stack and App"); setTitle("Select Target Stack and App"); setDescription(""); this.dataModel = dataModel; this.bindingContext = new DataBindingContext(); this.aggregateValidationStatus = new AggregateValidationStatus( bindingContext, AggregateValidationStatus.MAX_SEVERITY); } public void createControl(Composite parent) { Composite composite = new Composite(parent, SWT.NONE); composite.setLayout(new GridLayout(1, false)); createRegionSection(composite); createStackSection(composite); createJavaAppSection(composite); bindControls(); initializeValidators(); initializeDefaults(); onRegionSelectionChange(); setControl(composite); setPageComplete(false); } private void createRegionSection(Composite composite) { Group regionGroup = newGroup(composite, "Select AWS Region"); regionGroup.setLayout(new GridLayout(1, false)); regionCombo = newCombo(regionGroup); for (Region region : RegionUtils.getRegionsForService(ServiceAbbreviations.OPSWORKS)) { regionCombo.add(region.getName()); regionCombo.setData(region.getName(), region); } // Find the default region selection Region selectedRegion = dataModel.getRegion(); if (selectedRegion == null) { if ( RegionUtils.isServiceSupportedInCurrentRegion(ServiceAbbreviations.OPSWORKS) ) { selectedRegion = RegionUtils.getCurrentRegion(); } else { selectedRegion = RegionUtils.getRegion(OpsWorksPlugin.DEFAULT_REGION); } } regionCombo.setText(selectedRegion.getName()); regionCombo.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { onRegionSelectionChange(); } }); newFillingLabel(regionGroup, "Select the AWS region where your OpsWorks stack and app was created."); } private void createStackSection(Composite composite) { Group stackGroup = newGroup(composite, "Select OpsWorks stack:"); stackGroup.setLayout(new GridLayout(1, false)); stackNameCombo = newCombo(stackGroup); stackNameCombo.setEnabled(false); stackNameCombo.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { onStackSelectionChange(); } }); stackSelectionMessageLabel = newLink(stackGroup, webLinkListener, "", 1); } private void createJavaAppSection(Composite composite) { Group javaAppGroupSection = newGroup(composite, "Select or create a Java app:"); javaAppGroupSection.setLayout(new GridLayout(2, false)); useExistingJavaAppRadioButton = newRadioButton(javaAppGroupSection, "Choose an existing Java app:", 1, true, javaAppSectionRadioButtonSelectionListener); existingJavaAppNameCombo = newCombo(javaAppGroupSection); existingJavaAppNameCombo.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { onJavaAppSelectionChange(); } }); createNewJavaAppRadioButton = newRadioButton(javaAppGroupSection, "Create a new Java app:", 1, true, javaAppSectionRadioButtonSelectionListener); newApplicationNameText = newText(javaAppGroupSection); newApplicationNameDecoration = newControlDecoration( newApplicationNameText, "Enter a new app name or select an existing app."); } private void onRegionSelectionChange() { Region region = (Region)regionCombo.getData(regionCombo.getText()); String endpoint = region.getServiceEndpoints() .get(ServiceAbbreviations.OPSWORKS); opsworksClient = AwsToolkitCore.getClientFactory() .getOpsWorksClientByEndpoint(endpoint); dataModel.setRegion(region); refreshStacks(); } private void onStackSelectionChange() { if (existingStackLoaded.getValue().equals(Boolean.TRUE)) { Stack stack = (Stack)stackNameCombo.getData(stackNameCombo.getText()); dataModel.setExistingStack(stack); refreshJavaApps(); } } private void onJavaAppSelectionChange() { if (existingJavaAppLoaded.getValue().equals(Boolean.TRUE)) { App selectedApp = (App) existingJavaAppNameCombo .getData(existingJavaAppNameCombo.getText()); dataModel.setExistingJavaApp(selectedApp); } } private void refreshStacks() { existingStackLoaded.setValue(false); if (stackNameCombo != null) { stackNameCombo.setItems(new String[] {LOADING}); stackNameCombo.select(0); } if (stackSelectionMessageLabel != null) { stackSelectionMessageLabel.setText(""); } CancelableThread.cancelThread(loadStacksThread); loadStacksThread = new LoadStacksThread(); loadStacksThread.start(); } private void refreshJavaApps() { existingJavaAppLoaded.setValue(false); // Disable all the UI widgets for app selection until the apps are // loaded setJavaAppSectionEnabled(false); if (existingJavaAppNameCombo != null) { existingJavaAppNameCombo.setItems(new String[] {LOADING}); existingJavaAppNameCombo.select(0); } CancelableThread.cancelThread(loadJavaAppsThread); loadJavaAppsThread = new LoadJavaAppsThread(); loadJavaAppsThread.start(); } private void radioButtonSelected(Object source) { if ( source == useExistingJavaAppRadioButton || source == createNewJavaAppRadioButton) { boolean isCreatingNewApp = (Boolean) createNewJavaAppRadioButtonObservable.getValue(); existingJavaAppNameCombo.setEnabled(!isCreatingNewApp); newApplicationNameText.setEnabled(isCreatingNewApp); } } private void bindControls() { useExistingJavaAppRadioButtonObservable = SWTObservables .observeSelection(useExistingJavaAppRadioButton); createNewJavaAppRadioButtonObservable = SWTObservables .observeSelection(createNewJavaAppRadioButton); bindingContext.bindValue( createNewJavaAppRadioButtonObservable, PojoObservables.observeValue( dataModel, DeployProjectToOpsworksWizardDataModel.IS_CREATING_NEW_JAVA_APP)); newApplicationNameTextObservable = SWTObservables .observeText(newApplicationNameText, SWT.Modify); bindingContext.bindValue( newApplicationNameTextObservable, PojoObservables.observeValue( dataModel, DeployProjectToOpsworksWizardDataModel.NEW_JAVA_APP_NAME)); } private void initializeValidators() { // Bind the validation status to the wizard page message aggregateValidationStatus.addChangeListener(new IChangeListener() { public void handleChange(ChangeEvent arg0) { Object value = aggregateValidationStatus.getValue(); if (value instanceof IStatus == false) return; IStatus status = (IStatus)value; boolean success = (status.getSeverity() == IStatus.OK); setPageComplete(success); if (success) { setMessage("", IStatus.OK); } else { setMessage(status.getMessage(), IStatus.ERROR); } } }); // Validation status providers bindingContext.addValidationStatusProvider(new ChainValidator<Boolean>( existingStackLoaded, new BooleanValidator("Please select a stack"))); bindingContext.addValidationStatusProvider(new ChainValidator<Boolean>( existingJavaAppLoaded, useExistingJavaAppRadioButtonObservable, // enabler new BooleanValidator("Please select a Java app"))); ChainValidator<String> appNameValidator = new ChainValidator<String>( newApplicationNameTextObservable, createNewJavaAppRadioButtonObservable, // enabler new NotEmptyValidator("Please provide a valid app name")); bindingContext.addValidationStatusProvider(appNameValidator); new DecorationChangeListener(newApplicationNameDecoration, appNameValidator.getValidationStatus()); } private void runValidators() { Iterator<?> iterator = bindingContext.getBindings().iterator(); while (iterator.hasNext()) { Binding binding = (Binding)iterator.next(); binding.updateTargetToModel(); } } private void initializeDefaults() { existingStackLoaded.setValue(false); existingJavaAppLoaded.setValue(false); useExistingJavaAppRadioButtonObservable.setValue(true); createNewJavaAppRadioButtonObservable.setValue(false); newApplicationNameTextObservable.setValue("My App"); radioButtonSelected(useExistingJavaAppRadioButton); } private final class LoadStacksThread extends CancelableThread { @Override public void run() { final List<String> stackNames = new ArrayList<String>(); final Map<String, Stack> stacks = new HashMap<String, Stack>(); try { for (Stack stack : ServiceAPIUtils.getAllStacks(opsworksClient)) { stackNames.add(stack.getName()); stacks.put(stack.getName(), stack); } // Sort by name Collections.sort(stackNames); } catch (Exception e) { OpsWorksPlugin.getDefault().reportException( "Unable to load existing stacks.", e); setRunning(false); return; } Display.getDefault().asyncExec(new Runnable() { public void run() { try { synchronized (LoadStacksThread.this) { if ( !isCanceled() ) { stackNameCombo.removeAll(); for ( String stackName : stackNames ) { stackNameCombo.add(stackName); stackNameCombo.setData(stackName, stacks.get(stackName)); } if ( stackNames.size() > 0 ) { existingStackLoaded.setValue(true); stackNameCombo.setEnabled(true); stackNameCombo.select(0); onStackSelectionChange(); } else { existingStackLoaded.setValue(false); stackNameCombo.setEnabled(false); stackNameCombo.setItems(new String[] { NONE_FOUND}); stackNameCombo.select(0); stackSelectionMessageLabel.setText( "No stack is found in this region. " + "Please create a new OpsWorks stack via " + "<a href=\"" + UrlConstants.OPSWORKS_CONSOLE_URL + "\">AWS Console</a> before making a deployment"); useExistingJavaAppRadioButton.setEnabled(false); existingJavaAppNameCombo.setEnabled(false); existingJavaAppNameCombo.setItems(new String[0]); existingJavaAppLoaded.setValue(false); createNewJavaAppRadioButton.setEnabled(false); } // Re-calculate UI layout ((Composite)getControl()).layout(); } } } finally { setRunning(false); } } }); } } private final class LoadJavaAppsThread extends CancelableThread { @Override public void run() { final List<String> javaAppUINames = new ArrayList<String>(); final Map<String, App> javaApps = new HashMap<String,App>(); try { String stackId = dataModel.getExistingStack().getStackId(); for (App javaApp : ServiceAPIUtils .getAllJavaAppsInStack(opsworksClient, stackId)) { // UI names format : "app-name (app-shortname)" String appUIName = String.format("%s (%s)", javaApp.getName(), javaApp.getShortname()); javaAppUINames.add(appUIName); javaApps.put(appUIName, javaApp); } Collections.sort(javaAppUINames); } catch (Exception e) { Status status = new Status(Status.ERROR, OpsWorksPlugin.PLUGIN_ID, "Unable to load existing Java Apps: " + e.getMessage(), e); StatusManager.getManager().handle(status, StatusManager.SHOW); setRunning(false); return; } Display.getDefault().asyncExec(new Runnable() { public void run() { try { synchronized (LoadJavaAppsThread.this) { if ( !isCanceled() ) { // Restore the UI enabled status of the app section setJavaAppSectionEnabled(true); radioButtonSelected(useExistingJavaAppRadioButton); existingJavaAppNameCombo.removeAll(); for ( String javaAppUIName : javaAppUINames ) { existingJavaAppNameCombo.add(javaAppUIName); existingJavaAppNameCombo.setData(javaAppUIName, javaApps.get(javaAppUIName)); } if ( javaAppUINames.size() > 0 ) { existingJavaAppNameCombo.select(0); existingJavaAppLoaded.setValue(true); onJavaAppSelectionChange(); } else { useExistingJavaAppRadioButton.setEnabled(false); existingJavaAppNameCombo.setEnabled(false); existingJavaAppNameCombo.setItems(new String[] { NONE_FOUND}); existingJavaAppNameCombo.select(0); existingJavaAppLoaded.setValue(false); useExistingJavaAppRadioButtonObservable.setValue(false); createNewJavaAppRadioButtonObservable.setValue(true); radioButtonSelected(useExistingJavaAppRadioButton); } // Re-calculate UI layout ((Composite)getControl()).layout(); } } } finally { setRunning(false); } } }); } } /** * Enable/disable all the UI widgets in the app selection section. */ private void setJavaAppSectionEnabled(boolean enabled) { if (createNewJavaAppRadioButton != null) createNewJavaAppRadioButton.setEnabled(enabled); if (newApplicationNameText != null) newApplicationNameText.setEnabled(enabled); if (useExistingJavaAppRadioButton != null) useExistingJavaAppRadioButton.setEnabled(enabled); if (existingJavaAppNameCombo != null) existingJavaAppNameCombo.setEnabled(enabled); } @Override protected void onEnterPage() { } }