com.amazonaws.eclipse.explorer.cloudformation.wizard.CreateStackWizardFirstPage.java Source code

Java tutorial

Introduction

Here is the source code for com.amazonaws.eclipse.explorer.cloudformation.wizard.CreateStackWizardFirstPage.java

Source

/*
 * Copyright 2012 Amazon Technologies, 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://aws.amazon.com/apache2.0
 *
 * This file 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.amazonaws.eclipse.explorer.cloudformation.wizard;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.map.ObjectMapper;
import org.eclipse.core.databinding.AggregateValidationStatus;
import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.core.databinding.UpdateValueStrategy;
import org.eclipse.core.databinding.ValidationStatusProvider;
import org.eclipse.core.databinding.beans.PojoObservables;
import org.eclipse.core.databinding.conversion.IConverter;
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.databinding.validation.IValidator;
import org.eclipse.core.databinding.validation.ValidationStatus;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.databinding.swt.SWTObservables;
import org.eclipse.jface.fieldassist.ControlDecoration;
import org.eclipse.jface.fieldassist.FieldDecoration;
import org.eclipse.jface.fieldassist.FieldDecorationRegistry;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Link;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Text;

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.ui.CancelableThread;
import com.amazonaws.eclipse.core.ui.WebLinkListener;
import com.amazonaws.eclipse.databinding.ChainValidator;
import com.amazonaws.eclipse.databinding.DecorationChangeListener;
import com.amazonaws.eclipse.databinding.NotEmptyValidator;
import com.amazonaws.eclipse.explorer.cloudformation.wizard.CreateStackWizardDataModel.Mode;
import com.amazonaws.eclipse.explorer.sns.CreateTopicDialog;
import com.amazonaws.services.cloudformation.AmazonCloudFormation;
import com.amazonaws.services.cloudformation.model.DescribeStacksRequest;
import com.amazonaws.services.cloudformation.model.DescribeStacksResult;
import com.amazonaws.services.cloudformation.model.ListStacksResult;
import com.amazonaws.services.cloudformation.model.Parameter;
import com.amazonaws.services.cloudformation.model.Stack;
import com.amazonaws.services.cloudformation.model.StackSummary;
import com.amazonaws.services.cloudformation.model.TemplateParameter;
import com.amazonaws.services.cloudformation.model.ValidateTemplateRequest;
import com.amazonaws.services.cloudformation.model.ValidateTemplateResult;
import com.amazonaws.services.sns.AmazonSNS;
import com.amazonaws.services.sns.model.CreateTopicRequest;
import com.amazonaws.services.sns.model.ListTopicsResult;
import com.amazonaws.services.sns.model.Topic;

/**
 * The first page of the stack creation wizard, which prompts for a name and
 * template.
 */
class CreateStackWizardFirstPage extends WizardPage {

    private static final String LOADING_STACKS = "Loading stacks...";
    private static final String OK_MESSAGE = "Provide a name and a template for your new stack.";
    private static final String ESTIMATE_COST_OK_MESSAGE = "Provide a template to esitmate the cost";
    private static final String VALIDATING = "validating";
    private static final String INVALID = "invalid";
    private static final String VALID = "valid";

    /*
     * Data model
     */
    private IObservableValue stackName;
    private IObservableValue templateUrl;
    private IObservableValue templateFile;
    private IObservableValue useTemplateFile;
    private IObservableValue useTemplateUrl;
    private IObservableValue snsTopicArn;
    private IObservableValue notifyWithSNS;
    private IObservableValue timeoutMinutes;
    private IObservableValue rollbackOnFailure;
    private IObservableValue templateValidated = new WritableValue();
    private final DataBindingContext bindingContext = new DataBindingContext();

    private boolean complete = false;
    private ValidateTemplateThread validateTemplateThread;
    private LoadStackNamesThread loadStackNamesThread;
    private Exception templateValidationException;

    private Text fileTemplateText;
    private Text templateURLText;

    private CreateStackWizard wizard;

    protected CreateStackWizardFirstPage(CreateStackWizard createStackWizard) {
        super("");
        wizard = createStackWizard;
        if (wizard.getDataModel().getMode() == Mode.EstimateCost) {
            setMessage(ESTIMATE_COST_OK_MESSAGE);
        } else {
            setMessage(OK_MESSAGE);
        }

        stackName = PojoObservables.observeValue(wizard.getDataModel(), "stackName");
        templateUrl = PojoObservables.observeValue(wizard.getDataModel(), "templateUrl");
        templateFile = PojoObservables.observeValue(wizard.getDataModel(), "templateFile");
        useTemplateFile = PojoObservables.observeValue(wizard.getDataModel(), "useTemplateFile");
        useTemplateUrl = PojoObservables.observeValue(wizard.getDataModel(), "useTemplateUrl");
        notifyWithSNS = PojoObservables.observeValue(wizard.getDataModel(), "notifyWithSNS");
        snsTopicArn = PojoObservables.observeValue(wizard.getDataModel(), "snsTopicArn");
        timeoutMinutes = PojoObservables.observeValue(wizard.getDataModel(), "timeoutMinutes");
        rollbackOnFailure = PojoObservables.observeValue(wizard.getDataModel(), "rollbackOnFailure");
    }

    /*
     * (non-Javadoc)
     *
     * @see org.eclipse.jface.dialogs.IDialogPage#createControl(org.eclipse.swt
     * .widgets.Composite)
     */
    public void createControl(Composite parent) {
        final Composite comp = new Composite(parent, SWT.NONE);
        GridDataFactory.fillDefaults().grab(true, true).applyTo(comp);
        GridLayoutFactory.fillDefaults().numColumns(3).applyTo(comp);

        // Unfortunately, we have to manually adjust for field decorations
        FieldDecoration fieldDecoration = FieldDecorationRegistry.getDefault()
                .getFieldDecoration(FieldDecorationRegistry.DEC_ERROR);
        int fieldDecorationWidth = fieldDecoration.getImage().getBounds().width;
        if (wizard.getDataModel().getMode() != Mode.EstimateCost) {
            createStackNameControl(comp, fieldDecorationWidth);
        }
        createTemplateSelectionControl(comp, fieldDecorationWidth);

        // Some fields are only for creation, not update
        if (wizard.getDataModel().getMode() == Mode.Create) {
            createSNSTopicControl(comp, fieldDecorationWidth);
            createTimeoutControl(comp);
            createRollbackControl(comp);
        }

        setUpValidation(comp);

        // Set initial values for radio buttons
        useTemplateFile.setValue(true);
        templateValidated.setValue(null);
        // If we already have a file template filled in, validate it
        if (wizard.getDataModel().isUsePreselectedTemplateFile()) {
            validateTemplateFile((String) templateFile.getValue());
        }

        setControl(comp);
    }

    private void createStackNameControl(final Composite comp, int fieldDecorationWidth) {
        // Whether the user already set the stack name.
        boolean stackNameExists = false;
        new Label(comp, SWT.READ_ONLY).setText("Stack Name: ");
        Control stackNameControl = null;
        if (wizard.getDataModel().getMode() == Mode.Create) {
            Text stackNameText = new Text(comp, SWT.BORDER);
            bindingContext.bindValue(SWTObservables.observeText(stackNameText, SWT.Modify), stackName)
                    .updateTargetToModel();
            stackNameControl = stackNameText;
        } else {
            Combo combo = new Combo(comp, SWT.READ_ONLY | SWT.DROP_DOWN);

            if (stackName.getValue() != null) {
                combo.setItems(new String[] { (String) stackName.getValue() });
                stackNameExists = true;
            } else {
                combo.setItems(new String[] { LOADING_STACKS });
            }

            combo.select(0);
            bindingContext.bindValue(SWTObservables.observeSelection(combo), stackName).updateTargetToModel();
            stackNameControl = combo;

            stackName.addChangeListener(new IChangeListener() {
                public void handleChange(ChangeEvent event) {
                    if ((Boolean) useTemplateFile.getValue()) {
                        validateTemplateFile((String) templateFile.getValue());
                    } else {
                        validateTemplateUrl((String) templateUrl.getValue());
                    }
                }
            });

            if (!stackNameExists) {
                loadStackNames(combo);
            }
        }

        GridDataFactory.fillDefaults().grab(true, false).span(2, 1).indent(fieldDecorationWidth, 0)
                .applyTo(stackNameControl);
        ChainValidator<String> stackNameValidationStatusProvider = new ChainValidator<String>(stackName,
                new NotEmptyValidator("Please provide a stack name"));
        bindingContext.addValidationStatusProvider(stackNameValidationStatusProvider);
        addStatusDecorator(stackNameControl, stackNameValidationStatusProvider);
    }

    /**
     * Loads the names of all the stacks asynchronously.
     */
    private void loadStackNames(Combo combo) {
        CancelableThread.cancelThread(loadStackNamesThread);
        templateValidated.setValue(VALIDATING);
        loadStackNamesThread = new LoadStackNamesThread(combo);
        loadStackNamesThread.start();
    }

    private final class LoadStackNamesThread extends CancelableThread {

        private Combo combo;

        private LoadStackNamesThread(Combo combo) {
            super();
            this.combo = combo;
        }

        public void run() {
            AmazonCloudFormation cf = getCloudFormationClient();
            ListStacksResult listStacks = cf.listStacks();
            final List<String> stackNames = new ArrayList<String>();
            for (StackSummary summary : listStacks.getStackSummaries()) {
                stackNames.add(summary.getStackName());
            }

            Display.getDefault().syncExec(new Runnable() {

                public void run() {
                    try {
                        synchronized (this) {
                            if (!isCanceled()) {
                                combo.setItems(stackNames.toArray(new String[stackNames.size()]));
                                combo.select(0);
                                templateValidated.setValue(VALID);
                            }
                        }
                    } finally {
                        setRunning(false);
                    }
                }
            });

        }
    }

    private void createTemplateSelectionControl(final Composite comp, int fieldDecorationWidth) {
        Group stackTemplateSourceGroup = new Group(comp, SWT.NONE);
        GridDataFactory.fillDefaults().grab(true, false).span(3, 1).indent(fieldDecorationWidth, 0)
                .applyTo(stackTemplateSourceGroup);
        GridLayoutFactory.fillDefaults().numColumns(3).applyTo(stackTemplateSourceGroup);
        stackTemplateSourceGroup.setText("Stack Template Source:");

        createTemplateFileControl(stackTemplateSourceGroup, fieldDecorationWidth);
        createTemplateUrlControl(stackTemplateSourceGroup, fieldDecorationWidth);
    }

    private void createTemplateUrlControl(Group stackTemplateSourceGroup, int fieldDecorationWidth) {
        final Button templateUrlOption = new Button(stackTemplateSourceGroup, SWT.RADIO);
        templateUrlOption.setText("Template URL: ");
        templateURLText = new Text(stackTemplateSourceGroup, SWT.BORDER);
        templateURLText.setEnabled(false);
        GridDataFactory.fillDefaults().grab(true, false).indent(fieldDecorationWidth, 0).applyTo(templateURLText);
        Link link = new Link(stackTemplateSourceGroup, SWT.None);
        String sampleUrl = "http://docs.aws.amazon.com/cloudformation/aws-cloudformation-templates/";

        // TODO: this should really live in the regions file, not hardcoded here
        Region currentRegion = RegionUtils.getCurrentRegion();
        if (currentRegion.getId().equals("us-east-1")) {
            sampleUrl = "http://docs.aws.amazon.com/cloudformation/aws-cloudformation-templates/";
        } else {
            sampleUrl = "http://docs.aws.amazon.com/cloudformation/aws-cloudformation-templates/aws-cloudformation-templates-"
                    + currentRegion.getId() + "/";
        }

        link.setText("<a href=\"" + sampleUrl + "\">Browse for samples</a>");
        link.addListener(SWT.Selection, new WebLinkListener());

        bindingContext.bindValue(SWTObservables.observeText(templateURLText, SWT.Modify), templateUrl)
                .updateTargetToModel();
        bindingContext.bindValue(SWTObservables.observeSelection(templateUrlOption), useTemplateUrl)
                .updateTargetToModel();
        ChainValidator<String> templateUrlValidationStatusProvider = new ChainValidator<String>(templateUrl,
                useTemplateUrl, new NotEmptyValidator("Please provide a valid URL for your template"));
        bindingContext.addValidationStatusProvider(templateUrlValidationStatusProvider);
        addStatusDecorator(templateURLText, templateUrlValidationStatusProvider);

        templateUrlOption.addSelectionListener(new SelectionAdapter() {

            @Override
            public void widgetSelected(SelectionEvent e) {
                boolean selected = templateUrlOption.getSelection();
                templateURLText.setEnabled(selected);
            }
        });
    }

    private void createTemplateFileControl(Group stackTemplateSourceGroup, int fieldDecorationWidth) {
        Button fileTemplateOption = new Button(stackTemplateSourceGroup, SWT.RADIO);
        fileTemplateOption.setText("Template File: ");
        fileTemplateOption.setSelection(true);

        fileTemplateText = new Text(stackTemplateSourceGroup, SWT.BORDER | SWT.READ_ONLY);

        GridDataFactory.fillDefaults().grab(true, false).indent(fieldDecorationWidth, 0).applyTo(fileTemplateText);
        Button browseButton = new Button(stackTemplateSourceGroup, SWT.PUSH);
        browseButton.setText("Browse...");
        Listener fileTemplateSelectionListener = new Listener() {

            public void handleEvent(Event event) {
                if ((Boolean) useTemplateFile.getValue()) {
                    FileDialog dialog = new FileDialog(getShell(), SWT.OPEN);
                    String result = dialog.open();
                    if (result != null) {
                        fileTemplateText.setText(result);
                    }
                }
            }
        };
        browseButton.addListener(SWT.Selection, fileTemplateSelectionListener);
        fileTemplateText.addListener(SWT.MouseUp, fileTemplateSelectionListener);

        bindingContext.bindValue(SWTObservables.observeSelection(fileTemplateOption), useTemplateFile)
                .updateTargetToModel();
        bindingContext.bindValue(SWTObservables.observeText(fileTemplateText, SWT.Modify), templateFile)
                .updateTargetToModel();
        ChainValidator<String> templateFileValidationStatusProvider = new ChainValidator<String>(templateFile,
                useTemplateFile, new NotEmptyValidator("Please provide a valid file for your template"));
        bindingContext.addValidationStatusProvider(templateFileValidationStatusProvider);
        addStatusDecorator(fileTemplateText, templateFileValidationStatusProvider);
    }

    private void createSNSTopicControl(final Composite comp, int fieldDecorationWidth) {
        final Button notifyWithSNSButton = new Button(comp, SWT.CHECK);
        notifyWithSNSButton.setText("SNS Topic (Optional):");
        final Combo snsTopicCombo = new Combo(comp, SWT.DROP_DOWN | SWT.READ_ONLY);
        GridDataFactory.fillDefaults().grab(true, false).indent(fieldDecorationWidth, 0).applyTo(snsTopicCombo);
        loadTopics(snsTopicCombo);
        bindingContext.bindValue(SWTObservables.observeSelection(notifyWithSNSButton), notifyWithSNS)
                .updateTargetToModel();
        bindingContext.bindValue(SWTObservables.observeSelection(snsTopicCombo), snsTopicArn).updateTargetToModel();
        ChainValidator<String> snsTopicValidationStatusProvider = new ChainValidator<String>(snsTopicArn,
                notifyWithSNS, new NotEmptyValidator("Please select an SNS notification topic"));
        bindingContext.addValidationStatusProvider(snsTopicValidationStatusProvider);
        addStatusDecorator(snsTopicCombo, snsTopicValidationStatusProvider);

        final Button newTopicButton = new Button(comp, SWT.PUSH);
        newTopicButton.setText("Create New Topic");
        newTopicButton.addSelectionListener(new SelectionAdapter() {

            @Override
            public void widgetSelected(SelectionEvent e) {
                CreateTopicDialog dialog = new CreateTopicDialog();
                if (dialog.open() == 0) {
                    try {
                        AwsToolkitCore.getClientFactory().getSNSClient()
                                .createTopic(new CreateTopicRequest().withName(dialog.getTopicName()));
                    } catch (Exception ex) {
                        AwsToolkitCore.getDefault().logException("Failed to create new topic", ex);
                    }
                    loadTopics(snsTopicCombo);
                }
            }
        });

        snsTopicCombo.setEnabled(false);
        newTopicButton.setEnabled(false);
        notifyWithSNSButton.addSelectionListener(new SelectionAdapter() {

            @Override
            public void widgetSelected(SelectionEvent e) {
                boolean selection = notifyWithSNSButton.getSelection();
                snsTopicCombo.setEnabled(selection);
                newTopicButton.setEnabled(selection);
            }
        });
    }

    private void createTimeoutControl(final Composite comp) {
        new Label(comp, SWT.None).setText("Creation Timeout:");
        Combo timeoutCombo = new Combo(comp, SWT.DROP_DOWN | SWT.READ_ONLY);
        GridDataFactory.fillDefaults().grab(false, false).span(2, 1).applyTo(timeoutCombo);
        timeoutCombo.setItems(new String[] { "None", "5 minutes", "10 minutes", "15 minutes", "20 minutes",
                "30 minutes", "60 minutes", "90 minutes", });
        timeoutCombo.select(0);
        UpdateValueStrategy timeoutUpdateStrategy = new UpdateValueStrategy(UpdateValueStrategy.POLICY_UPDATE);
        timeoutUpdateStrategy.setConverter(new IConverter() {

            public Object getToType() {
                return Integer.class;
            }

            public Object getFromType() {
                return String.class;
            }

            public Object convert(Object fromObject) {
                String value = (String) fromObject;
                if ("None".equals(value)) {
                    return 0;
                } else {
                    String minutes = value.substring(0, value.indexOf(' '));
                    return Integer.parseInt(minutes);
                }
            }
        });
        bindingContext.bindValue(SWTObservables.observeSelection(timeoutCombo), timeoutMinutes,
                timeoutUpdateStrategy, new UpdateValueStrategy(UpdateValueStrategy.POLICY_NEVER))
                .updateTargetToModel();
    }

    private void createRollbackControl(final Composite comp) {
        Button rollbackButton = new Button(comp, SWT.CHECK);
        rollbackButton.setText("Rollback on Failure");
        GridDataFactory.fillDefaults().grab(false, false).span(3, 1).applyTo(rollbackButton);
        rollbackButton.setSelection(true);
        bindingContext.bindValue(SWTObservables.observeSelection(rollbackButton), rollbackOnFailure)
                .updateTargetToModel();
    }

    private void setUpValidation(final Composite comp) {

        // Change listeners to re-validate the template whenever
        // the customer changes whether to use a file or a URL
        templateUrl.addChangeListener(new IChangeListener() {

            public void handleChange(ChangeEvent event) {
                if (((String) templateUrl.getValue()).length() > 0) {
                    validateTemplateUrl((String) templateUrl.getValue());
                }
            }
        });
        useTemplateUrl.addChangeListener(new IChangeListener() {

            public void handleChange(ChangeEvent event) {
                if ((Boolean) useTemplateUrl.getValue() && ((String) templateUrl.getValue()).length() > 0) {
                    validateTemplateUrl((String) templateUrl.getValue());
                }
            }
        });

        templateFile.addChangeListener(new IChangeListener() {

            public void handleChange(ChangeEvent event) {
                validateTemplateFile((String) templateFile.getValue());
            }
        });
        useTemplateFile.addChangeListener(new IChangeListener() {

            public void handleChange(ChangeEvent event) {
                if ((Boolean) useTemplateFile.getValue()) {
                    validateTemplateFile((String) templateFile.getValue());
                }
            }
        });

        // Status validator for template validation, which occurs out of band
        IValidator templateValidator = new IValidator() {

            public IStatus validate(Object value) {
                if (value == null) {
                    return ValidationStatus.error("No template selected");
                }

                if (((String) value).equals(VALID)) {
                    return ValidationStatus.ok();
                } else if (((String) value).equals(VALIDATING)) {
                    return ValidationStatus.warning("Validating template...");
                } else if (((String) value).equals(INVALID)) {
                    if (templateValidationException != null) {
                        return ValidationStatus
                                .error("Invalid template: " + templateValidationException.getMessage());
                    } else {
                        return ValidationStatus.error("No template selected");
                    }
                }

                return ValidationStatus.ok();
            }
        };
        bindingContext
                .addValidationStatusProvider(new ChainValidator<String>(templateValidated, templateValidator));

        // Also hook up this template validator to the two template fields
        // conditionally
        addStatusDecorator(fileTemplateText,
                new ChainValidator<String>(templateValidated, useTemplateFile, templateValidator));
        addStatusDecorator(templateURLText,
                new ChainValidator<String>(templateValidated, useTemplateUrl, templateValidator));

        // Finally provide aggregate status reporting for the entire wizard page
        final AggregateValidationStatus aggregateValidationStatus = new AggregateValidationStatus(bindingContext,
                AggregateValidationStatus.MAX_SEVERITY);

        aggregateValidationStatus.addChangeListener(new IChangeListener() {

            public void handleChange(ChangeEvent event) {
                Object value = aggregateValidationStatus.getValue();
                if (value instanceof IStatus == false)
                    return;

                IStatus status = (IStatus) value;
                if (status.isOK()) {
                    setErrorMessage(null);
                    if (wizard.getDataModel().getMode() == Mode.EstimateCost) {
                        setMessage(ESTIMATE_COST_OK_MESSAGE, Status.OK);
                    } else {
                        setMessage(OK_MESSAGE, Status.OK);
                    }
                } else if (status.getSeverity() == Status.WARNING) {
                    setErrorMessage(null);
                    setMessage(status.getMessage(), Status.WARNING);
                } else if (status.getSeverity() == Status.ERROR) {
                    setErrorMessage(status.getMessage());
                }

                setComplete(status.isOK());
            }
        });
    }

    /**
     * Adds a control status decorator for the control given.
     */
    private void addStatusDecorator(final Control control, ValidationStatusProvider validationStatusProvider) {
        ControlDecoration decoration = new ControlDecoration(control, SWT.TOP | SWT.LEFT);
        decoration.setDescriptionText("Invalid value");
        FieldDecoration fieldDecoration = FieldDecorationRegistry.getDefault()
                .getFieldDecoration(FieldDecorationRegistry.DEC_ERROR);
        decoration.setImage(fieldDecoration.getImage());
        new DecorationChangeListener(decoration, validationStatusProvider.getValidationStatus());
    }

    @Override
    public boolean isPageComplete() {
        return complete;
    }

    private void setComplete(boolean complete) {
        this.complete = complete;
        if (getWizard().getContainer() != null && getWizard().getContainer().getCurrentPage() != null)
            getWizard().getContainer().updateButtons();
    }

    /**
     * Loads all SNS topics into the dropdown given
     */
    private void loadTopics(final Combo snsTopicCombo) {
        snsTopicCombo.setItems(new String[] { "Loading..." });
        new Thread() {

            @Override
            public void run() {
                AmazonSNS sns = AwsToolkitCore.getClientFactory().getSNSClient();
                ListTopicsResult topicsResult = sns.listTopics();
                final List<String> arns = new ArrayList<String>();
                for (Topic topic : topicsResult.getTopics()) {
                    arns.add(topic.getTopicArn());
                }
                Display.getDefault().syncExec(new Runnable() {

                    public void run() {
                        if (!snsTopicCombo.isDisposed()) {
                            snsTopicCombo.setItems(arns.toArray(new String[arns.size()]));
                        }
                    }
                });
            }

        }.start();
    }

    /**
     * Validates the template file given in a separate thread.
     */
    private void validateTemplateFile(String filePath) {
        CancelableThread.cancelThread(validateTemplateThread);
        templateValidated.setValue(VALIDATING);
        try {
            String fileContents = FileUtils.readFileToString(new File(filePath), "UTF8");
            validateTemplateThread = new ValidateTemplateThread(
                    new ValidateTemplateRequest().withTemplateBody(fileContents));
            validateTemplateThread.start();
        } catch (Exception e) {
            templateValidated.setValue(INVALID);
            templateValidationException = e;
        }
    }

    /**
     * Validates the template url given in a separate thread.
     */
    private void validateTemplateUrl(String url) {
        CancelableThread.cancelThread(validateTemplateThread);
        templateValidated.setValue(VALIDATING);
        validateTemplateThread = new ValidateTemplateThread(new ValidateTemplateRequest().withTemplateURL(url));
        validateTemplateThread.start();
    }

    /**
     * Cancelable thread to validate a template and update the validation
     * status.
     */
    private final class ValidateTemplateThread extends CancelableThread {

        private final ValidateTemplateRequest rq;

        private ValidateTemplateThread(ValidateTemplateRequest rq) {
            this.rq = rq;
        }

        @Override
        public void run() {
            ValidateTemplateResult validateTemplateResult;
            Stack existingStack = null;

            Map templateMap;
            try {

                // TODO: region should come from context for file-based actions
                AmazonCloudFormation cf = getCloudFormationClient();
                validateTemplateResult = cf.validateTemplate(rq);

                if (wizard.getDataModel().getMode() == Mode.Update
                        && wizard.getDataModel().getStackName() != LOADING_STACKS) {
                    DescribeStacksResult describeStacks = cf.describeStacks(
                            new DescribeStacksRequest().withStackName(wizard.getDataModel().getStackName()));
                    if (describeStacks.getStacks().size() == 1) {
                        existingStack = describeStacks.getStacks().iterator().next();
                    }
                }

                String templateBody = null;
                if (rq.getTemplateBody() != null) {
                    templateBody = rq.getTemplateBody();
                } else {
                    InputStream in = new URL(rq.getTemplateURL()).openStream();
                    try {
                        templateBody = IOUtils.toString(in);
                    } finally {
                        IOUtils.closeQuietly(in);
                    }
                }

                templateMap = parseTemplate(templateBody);
                wizard.getDataModel().setTemplateBody(templateBody);

            } catch (Exception e) {
                templateValidationException = e;
                Display.getDefault().syncExec(new Runnable() {
                    public void run() {
                        synchronized (this) {
                            if (!isCanceled()) {
                                templateValidated.setValue(INVALID);
                            }
                        }
                    }
                });
                setRunning(false);
                return;
            }

            final List<TemplateParameter> templateParams = validateTemplateResult.getParameters();
            final Map templateJson = templateMap;
            final List<String> requiredCapabilities = validateTemplateResult.getCapabilities();
            final Stack stack = existingStack;

            Display.getDefault().syncExec(new Runnable() {

                public void run() {
                    try {
                        synchronized (this) {
                            if (!isCanceled()) {
                                wizard.getDataModel().setTemplateParameters(templateParams);
                                wizard.setNeedsSecondPage(!templateParams.isEmpty());
                                wizard.getDataModel().setTemplate(templateJson);
                                wizard.getDataModel().setRequiredCapabilities(requiredCapabilities);
                                if (stack != null) {
                                    for (Parameter param : stack.getParameters()) {
                                        boolean noEcho = false;

                                        // This is a pain, but any "noEcho" parameters get returned as asterisks in the service response.
                                        // The customer must fill these values out again, even for a running stack.
                                        for (TemplateParameter templateParam : wizard.getDataModel()
                                                .getTemplateParameters()) {
                                            if (templateParam.getNoEcho() && templateParam.getParameterKey()
                                                    .equals(param.getParameterKey())) {
                                                noEcho = true;
                                                break;
                                            }
                                        }

                                        if (!noEcho) {
                                            wizard.getDataModel().getParameterValues().put(param.getParameterKey(),
                                                    param.getParameterValue());
                                        }
                                    }
                                }
                                templateValidated.setValue(VALID);
                            }
                        }
                    } finally {
                        setRunning(false);
                    }
                }
            });
        }
    }

    /**
     * Parses the (already validated) template given and returns a map of its
     * structure.
     */
    private Map parseTemplate(String templateBody) throws JsonParseException, IOException {
        ObjectMapper mapper = new ObjectMapper();
        return mapper.readValue(templateBody, Map.class);
    }

    /**
     * @return
     */
    private AmazonCloudFormation getCloudFormationClient() {
        AmazonCloudFormation cf = AwsToolkitCore.getClientFactory().getCloudFormationClient();
        return cf;
    }

    /**
     * Whether we can flip to the next page depends on whether there's one to go to.
     */
    @Override
    public boolean canFlipToNextPage() {
        return wizard.needsSecondPage() && super.canFlipToNextPage();
    }

}