com.amazonaws.eclipse.lambda.upload.wizard.page.FunctionConfigurationComposite.java Source code

Java tutorial

Introduction

Here is the source code for com.amazonaws.eclipse.lambda.upload.wizard.page.FunctionConfigurationComposite.java

Source

/*
 * Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 *  http://aws.amazon.com/apache2.0
 *
 * or in the "license" file accompanying this file. 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.lambda.upload.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.newText;
import static com.amazonaws.eclipse.lambda.LambdaAnalytics.ATTR_NAME_CHANGE_SELECTION;
import static com.amazonaws.eclipse.lambda.LambdaAnalytics.ATTR_NAME_CLICK;
import static com.amazonaws.eclipse.lambda.LambdaAnalytics.ATTR_VALUE_CREATE_IAM_ROLE_BUTTON;
import static com.amazonaws.eclipse.lambda.LambdaAnalytics.ATTR_VALUE_CREATE_S3_BUCKET_BUTTON;
import static com.amazonaws.eclipse.lambda.LambdaAnalytics.ATTR_VALUE_FUNCTION_HANDLER_SELECTION_COMBO;
import static com.amazonaws.eclipse.lambda.LambdaAnalytics.ATTR_VALUE_IAM_ROLE_SELECTION_COMBO;
import static com.amazonaws.eclipse.lambda.LambdaAnalytics.ATTR_VALUE_S3_BUCKET_SELECTION_COMBO;
import static com.amazonaws.eclipse.lambda.LambdaAnalytics.EVENT_TYPE_UPLOAD_FUNCTION_WIZARD;
import static com.amazonaws.eclipse.lambda.LambdaAnalytics.METRIC_NAME_LOAD_IAM_ROLE_TIME_DURATION_MS;
import static com.amazonaws.eclipse.lambda.LambdaAnalytics.METRIC_NAME_LOAD_S3_BUCKET_TIME_DURATION_MS;

import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
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.IChangeListener;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.core.databinding.observable.value.WritableValue;
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.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.layout.GridData;
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.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;

import com.amazonaws.eclipse.core.AwsToolkitCore;
import com.amazonaws.eclipse.core.mobileanalytics.ToolkitAnalyticsManager;
import com.amazonaws.eclipse.core.ui.CancelableThread;
import com.amazonaws.eclipse.databinding.BooleanValidator;
import com.amazonaws.eclipse.databinding.ChainValidator;
import com.amazonaws.eclipse.databinding.DecorationChangeListener;
import com.amazonaws.eclipse.databinding.RangeValidator;
import com.amazonaws.eclipse.lambda.LambdaPlugin;
import com.amazonaws.eclipse.lambda.ServiceApiUtils;
import com.amazonaws.eclipse.lambda.UrlConstants;
import com.amazonaws.eclipse.lambda.upload.wizard.dialog.CreateBasicLambdaRoleDialog;
import com.amazonaws.eclipse.lambda.upload.wizard.dialog.CreateS3BucketDialog;
import com.amazonaws.eclipse.lambda.upload.wizard.model.FunctionConfigPageDataModel;
import com.amazonaws.eclipse.lambda.upload.wizard.model.UploadFunctionWizardDataModel;
import com.amazonaws.services.identitymanagement.AmazonIdentityManagement;
import com.amazonaws.services.identitymanagement.model.Role;
import com.amazonaws.services.lambda.model.FunctionConfiguration;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.Bucket;

public class FunctionConfigurationComposite extends Composite {

    private static final int MIN_MEMORY = 128;
    private static final int MAX_MEMORY = 1536;
    private static final int DEFAULT_MEMORY = 512;

    private static final int MIN_TIMEOUT = 1;
    private static final int MAX_TIMEOUT = 60;
    private static final int DEFAULT_TIMEOUT = 15;

    private UploadFunctionWizardDataModel dataModel;
    private final DataBindingContext bindingContext;
    private final AggregateValidationStatus aggregateValidationStatus;

    /**
     * @see #setValidationStatusChangeListener(IChangeListener)
     * @see #removeValidationStatusChangeListener()
     */
    private IChangeListener validationStatusChangeListener;

    /* Basic settings */
    private Label functionNameLabel;
    private Text descriptionText;
    private ISWTObservableValue descriptionTextObservable;
    private IObservableValue descriptionModelObservable;

    /* Function execution setting */
    private Combo handlerCombo;
    private ISWTObservableValue handlerComboObservable;
    private IObservableValue handlerModelObservable;

    private Combo roleNameCombo;
    private IObservableValue roleLoadedObservable = new WritableValue();
    private Button createRoleButton;

    /* S3 bucket */
    private Combo bucketNameCombo;
    private ISWTObservableValue bucketNameComboObservable;
    private IObservableValue bucketNameModelObservable;
    private IObservableValue bucketNameLoadedObservable = new WritableValue();
    private LoadS3BucketsInFunctionRegionThread loadS3BucketsInFunctionRegionThread;
    private Button createBucketButton;

    /* Advanced settings */
    private Text memoryText;
    private ControlDecoration memoryTextDecoration;
    private ISWTObservableValue memoryTextObservable;
    private IObservableValue memoryModelObservable;
    private Text timeoutText;
    private ControlDecoration timeoutTextDecoration;
    private ISWTObservableValue timeoutTextObservable;
    private IObservableValue timeoutModelObservable;

    /* Constants */

    private static final String LOADING = "Loading...";
    private static final String NONE_FOUND = "None found";

    FunctionConfigurationComposite(Composite parent, UploadFunctionWizardDataModel dataModel) {
        super(parent, SWT.NONE);

        this.dataModel = dataModel;

        this.bindingContext = new DataBindingContext();
        this.aggregateValidationStatus = new AggregateValidationStatus(bindingContext,
                AggregateValidationStatus.MAX_SEVERITY);

        setLayout(new GridLayout(1, false));
        createControls(this);

        bindControls();
        initializeValidators();
        pupulateDefaultData();

        loadLambdaRolesAsync();

        // We defer loading S3 buckets until the user reaches the page
        // (after the function region is selected in the previous page).
    }

    /**
     * Set listener that will be notified whenever the validation status of this
     * composite is updated. This method removes the listener (if any) that is
     * currently registered to this composite - only one listener instance is
     * allowed at a time.
     */
    public synchronized void setValidationStatusChangeListener(IChangeListener listener) {
        removeValidationStatusChangeListener();
        validationStatusChangeListener = listener;
        aggregateValidationStatus.addChangeListener(listener);
    }

    /**
     * @see #setValidationStatusChangeListener(IChangeListener)
     */
    public synchronized void removeValidationStatusChangeListener() {
        if (validationStatusChangeListener != null) {
            aggregateValidationStatus.removeChangeListener(validationStatusChangeListener);
            validationStatusChangeListener = null;
        }
    }

    public void updateUIDataToModel() {
        Iterator<?> iterator = bindingContext.getBindings().iterator();
        while (iterator.hasNext()) {
            Binding binding = (Binding) iterator.next();
            binding.updateTargetToModel();
        }
    }

    private void createControls(Composite parent) {
        createBasicSettingSection(parent);
        createFunctionExecutionSettingSection(parent);
        createS3BucketSection(parent);
        createAdvancedSettingSection(parent);
    }

    private void createBasicSettingSection(Composite parent) {
        Group group = newGroup(parent, "Basic Settings");
        group.setLayout(createSectionGroupLayout());

        newLabel(group, "Name:");
        functionNameLabel = newFillingLabel(group, "", 2);

        newLabel(group, "Description:");
        descriptionText = newText(group, "", 2);
        descriptionText.setMessage("The description for the function (optional)");
    }

    private void createFunctionExecutionSettingSection(Composite parent) {
        Group group = newGroup(parent, "Function Execution");
        group.setLayout(createSectionGroupLayout());

        newLabel(group, "Handler:");
        handlerCombo = newCombo(group, 2);
        for (String handler : dataModel.getRequestHandlerImplementerClasses()) {
            handlerCombo.add(handler);
        }
        handlerCombo.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                trackFunctionHandlerComboSelectionChange();
            }
        });

        Label separator = new Label(group, SWT.HORIZONTAL | SWT.SEPARATOR);
        GridData separatorGridData = new GridData(GridData.FILL_HORIZONTAL);
        separatorGridData.horizontalSpan = 3;
        separator.setLayoutData(separatorGridData);

        setItalicFont(newLink(group, UrlConstants.webLinkListener,
                "Select the IAM role that AWS Lambda can assume to execute the function on your behalf. <a href=\""
                        + UrlConstants.LAMBDA_EXECUTION_ROLE_DOC_URL
                        + "\">Learn more</a> about Lambda execution roles.",
                3));

        newLabel(group, "IAM Role:");

        Composite roleComposite = new Composite(group, SWT.NONE);
        GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, false);
        gridData.horizontalSpan = 2;
        roleComposite.setLayoutData(gridData);
        roleComposite.setLayout(new GridLayout(2, false));

        roleNameCombo = newCombo(roleComposite, 1);
        roleNameCombo.setEnabled(false);

        roleNameCombo.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                trackRoleComboSelectionChange();
                onRoleSelectionChange();
            }
        });

        createRoleButton = new Button(roleComposite, SWT.PUSH);
        createRoleButton.setText("Create");
        createRoleButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {

                trackClickCreateNewRoleButton();

                CreateBasicLambdaRoleDialog dialog = new CreateBasicLambdaRoleDialog(
                        Display.getCurrent().getActiveShell());
                int returnCode = dialog.open();

                if (returnCode == 0) {
                    Role createdRole = dialog.getCreatedRole();

                    if (roleLoadedObservable.getValue().equals(Boolean.FALSE)) {
                        roleLoadedObservable.setValue(true);
                        roleNameCombo.removeAll(); // remove the "none found" item
                    }

                    roleNameCombo.setEnabled(true);
                    roleNameCombo.add(createdRole.getRoleName());
                    roleNameCombo.setData(createdRole.getRoleName(), createdRole);
                    roleNameCombo.select(roleNameCombo.getItemCount() - 1);

                    onRoleSelectionChange();
                }
            }
        });
        createRoleButton.setEnabled(false); // re-enabled after the roles are loaded

    }

    private void createS3BucketSection(Composite parent) {
        Group group = newGroup(parent, "S3 Bucket for Function Code");
        group.setLayout(createSectionGroupLayout());

        newLabel(group, "S3 Bucket:");

        Composite composite = new Composite(group, SWT.NONE);
        GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, false);
        gridData.horizontalSpan = 2;
        composite.setLayoutData(gridData);
        composite.setLayout(new GridLayout(2, false));

        bucketNameCombo = newCombo(composite, 1);
        bucketNameCombo.setEnabled(false);
        bucketNameCombo.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                trackS3BucketComboSelectionChange();
            }
        });

        createBucketButton = new Button(composite, SWT.PUSH);
        createBucketButton.setText("Create");
        createBucketButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {

                trackClickCreateNewBucketButton();

                CreateS3BucketDialog dialog = new CreateS3BucketDialog(Display.getCurrent().getActiveShell(),
                        dataModel.getRegion());
                int returnCode = dialog.open();

                if (returnCode == 0) {
                    String bucketName = dialog.getCreatedBucketName();

                    if (bucketNameLoadedObservable.getValue().equals(Boolean.TRUE)) {
                        bucketNameCombo.add(bucketName);
                        bucketNameCombo.select(bucketNameCombo.getItemCount() - 1);

                    } else {
                        CancelableThread.cancelThread(loadS3BucketsInFunctionRegionThread);
                        bucketNameLoadedObservable.setValue(true);

                        bucketNameCombo.setItems(new String[] { bucketName });
                        bucketNameCombo.setEnabled(true);
                        bucketNameCombo.select(0);
                        updateUIDataToModel();
                    }
                }
            }
        });
    }

    private void createAdvancedSettingSection(Composite parent) {
        Group group = newGroup(parent, "Advanced Settings");
        group.setLayout(createSectionGroupLayout());

        newLabel(group, "Memory (MB):");
        memoryText = newText(group, "", 2);
        memoryTextDecoration = newControlDecoration(memoryText, "");

        newLabel(group, "Timeout (s):");
        timeoutText = newText(group, "", 2);
        timeoutTextDecoration = newControlDecoration(timeoutText, "");
    }

    private GridLayout createSectionGroupLayout() {
        GridLayout layout = new GridLayout(3, true);
        layout.marginWidth = 15;
        return layout;
    }

    private void bindControls() {

        FunctionConfigPageDataModel createModel = dataModel.getFunctionConfigPageDataModel();

        descriptionTextObservable = SWTObservables.observeText(descriptionText, SWT.Modify);
        descriptionModelObservable = PojoObservables.observeValue(createModel,
                FunctionConfigPageDataModel.P_DESCRIPTION);
        bindingContext.bindValue(descriptionTextObservable, descriptionModelObservable);

        handlerComboObservable = SWTObservables.observeSelection(handlerCombo);
        handlerModelObservable = PojoObservables.observeValue(createModel, FunctionConfigPageDataModel.P_HANDLER);
        bindingContext.bindValue(handlerComboObservable, handlerModelObservable);

        bucketNameComboObservable = SWTObservables.observeSelection(bucketNameCombo);
        bucketNameModelObservable = PojoObservables.observeValue(createModel,
                FunctionConfigPageDataModel.P_BUCKET_NAME);
        bindingContext.bindValue(bucketNameComboObservable, bucketNameModelObservable);

        memoryTextObservable = SWTObservables.observeText(memoryText, SWT.Modify);
        memoryModelObservable = PojoObservables.observeValue(createModel, FunctionConfigPageDataModel.P_MEMORY);
        bindingContext.bindValue(memoryTextObservable, memoryModelObservable);

        timeoutTextObservable = SWTObservables.observeText(timeoutText, SWT.Modify);
        timeoutModelObservable = PojoObservables.observeValue(createModel, FunctionConfigPageDataModel.P_TIMEOUT);
        bindingContext.bindValue(timeoutTextObservable, timeoutModelObservable);
    }

    private void initializeValidators() {
        bindingContext.addValidationStatusProvider(new ChainValidator<Boolean>(roleLoadedObservable,
                new BooleanValidator("Please select an execution role for your function")));

        bindingContext.addValidationStatusProvider(new ChainValidator<Boolean>(bucketNameLoadedObservable,
                new BooleanValidator("Please select the S3 bucket where your function code will be uploaded")));

        String memoryErrMsg = String.format("Please enter a memory size within the range of [%d, %d]", MIN_MEMORY,
                MAX_MEMORY);
        ChainValidator<Long> memoryValidator = new ChainValidator<Long>(memoryModelObservable,
                new RangeValidator(memoryErrMsg, MIN_MEMORY, MAX_MEMORY));
        bindingContext.addValidationStatusProvider(memoryValidator);
        new DecorationChangeListener(memoryTextDecoration, memoryValidator.getValidationStatus());

        String timeoutErrMsg = String.format("Please enter a timeout within the range of [%d, %d]", MIN_TIMEOUT,
                MAX_TIMEOUT);
        ChainValidator<Long> timeoutValidator = new ChainValidator<Long>(timeoutModelObservable,
                new RangeValidator(timeoutErrMsg, MIN_TIMEOUT, MAX_TIMEOUT));
        bindingContext.addValidationStatusProvider(timeoutValidator);
        new DecorationChangeListener(timeoutTextDecoration, timeoutValidator.getValidationStatus());
    }

    public void populateNewFunctionName() {
        functionNameLabel.setText(dataModel.getNewFunctionName());
    }

    public void pupulateDefaultData() {
        descriptionTextObservable.setValue("");
        handlerModelObservable.setValue(dataModel.getRequestHandlerImplementerClasses().get(0));
        memoryTextObservable.setValue(Integer.toString(DEFAULT_MEMORY));
        timeoutTextObservable.setValue(Integer.toString(DEFAULT_TIMEOUT));
    }

    public void populateExistingFunctionConfig(FunctionConfiguration funcConfig) {
        functionNameLabel.setText(funcConfig.getFunctionName());

        if (funcConfig.getDescription() == null) {
            descriptionTextObservable.setValue("");
        } else {
            descriptionTextObservable.setValue(funcConfig.getDescription());
        }

        if (dataModel.getRequestHandlerImplementerClasses().contains(funcConfig.getHandler())) {
            handlerModelObservable.setValue(funcConfig.getHandler());
        } else {
            handlerModelObservable.setValue(dataModel.getRequestHandlerImplementerClasses().get(0));
        }

        selectRoleByArn(funcConfig.getRole());

        memoryTextObservable.setValue(funcConfig.getMemorySize().toString());
        timeoutTextObservable.setValue(funcConfig.getTimeout().toString());
    }

    /*
     * Async loading of S3 buckets. S3 buckets might be loaded multiple times
     * since it depends on the current region being selected
     */

    public void refreshBucketsInFunctionRegion() {
        bucketNameLoadedObservable.setValue(false);

        if (bucketNameCombo != null) {
            bucketNameCombo.setItems(new String[] { "Loading buckets in " + dataModel.getRegion().getName() });
            bucketNameCombo.select(0);
            bucketNameCombo.setEnabled(false);
        }

        CancelableThread.cancelThread(loadS3BucketsInFunctionRegionThread);
        loadS3BucketsInFunctionRegionThread = new LoadS3BucketsInFunctionRegionThread(
                getLastDeploymentBucketName());
        loadS3BucketsInFunctionRegionThread.start();
    }

    private String getLastDeploymentBucketName() {
        return this.dataModel.getProjectMetadataBeforeUpload() == null ? null
                : this.dataModel.getProjectMetadataBeforeUpload().getLastDeploymentBucketName();
    }

    private final class LoadS3BucketsInFunctionRegionThread extends CancelableThread {

        private final String defaultBucket;

        /**
         * @param defaultBucket
         *            the bucket that should be selected by default after all
         *            buckets are loaded.
         */
        LoadS3BucketsInFunctionRegionThread(String defaultBucket) {
            this.defaultBucket = defaultBucket;
        }

        @Override
        public void run() {

            long startTime = System.currentTimeMillis();
            AmazonS3 s3 = AwsToolkitCore.getClientFactory().getS3Client();
            final List<Bucket> bucketsInFunctionRegion = S3BucketUtil.listBucketsInRegion(s3,
                    dataModel.getRegion());
            trackLoadBucketTimeDuration(System.currentTimeMillis() - startTime);

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

                public void run() {
                    try {
                        synchronized (LoadS3BucketsInFunctionRegionThread.this) {
                            if (!isCanceled()) {

                                if (bucketsInFunctionRegion.isEmpty()) {
                                    bucketNameCombo.setItems(new String[] { NONE_FOUND });
                                    bucketNameLoadedObservable.setValue(false);

                                } else {
                                    bucketNameCombo.removeAll();
                                    for (Bucket bucket : bucketsInFunctionRegion) {
                                        bucketNameCombo.add(bucket.getName());
                                    }
                                    bucketNameCombo.setEnabled(true);
                                    bucketNameCombo.select(findDefaultBucket(bucketsInFunctionRegion));
                                    updateUIDataToModel();

                                    bucketNameLoadedObservable.setValue(true);
                                }
                            }
                        }
                    } finally {
                        setRunning(false);
                    }
                }
            });
        }

        private int findDefaultBucket(List<Bucket> buckets) {
            for (int i = 0; i < buckets.size(); i++) {
                if (buckets.get(i).getName().equals(this.defaultBucket)) {
                    return i;
                }
            }
            return 0;
        }
    }

    /*
     * Async loading of IAM roles. IAM roles are only loaded once.
     */

    private List<Role> allRolesSortedByName = new LinkedList<Role>();
    private String roleArnToBeSelectedAfterRolesAreLoaded;

    private void selectRoleByArn(String roleArn) {
        if (roleLoadedObservable.getValue().equals(Boolean.TRUE)) {
            // role already loaded, directly select the item
            doSelectRoleByArn(roleArn);
        } else {
            // keep track the ARN and set the selection after the roles are
            // loaded
            roleArnToBeSelectedAfterRolesAreLoaded = roleArn;
        }
    }

    private void doSelectRoleByArn(String roleArn) {
        int index = 0;
        for (Role role : allRolesSortedByName) {
            if (role.getArn().equals(roleArn)) {
                break;
            }
            index++;
        }
        if (index < allRolesSortedByName.size()) {
            roleNameCombo.select(index);
        }
        onRoleSelectionChange();
    }

    private void loadLambdaRolesAsync() {
        roleLoadedObservable.setValue(false);

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

            public void run() {
                roleNameCombo.setItems(new String[] { LOADING });
                roleNameCombo.select(0);
                roleNameCombo.setEnabled(false);
            }
        });

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

            public void run() {
                try {

                    long start = System.currentTimeMillis();
                    AmazonIdentityManagement iam = AwsToolkitCore.getClientFactory().getIAMClient();
                    List<Role> roles = ServiceApiUtils.getAllLambdaRoles(iam);
                    trackLoadRoleTimeDuration(System.currentTimeMillis() - start);

                    roleNameCombo.removeAll();

                    if (roles.isEmpty()) {
                        roleNameCombo.setItems(new String[] { NONE_FOUND });
                        roleLoadedObservable.setValue(false);

                    } else {
                        List<String> allRoleNames = new LinkedList<String>();
                        Map<String, Role> allRolesMap = new HashMap<String, Role>();
                        for (Role role : roles) {
                            allRoleNames.add(role.getRoleName());
                            allRolesMap.put(role.getRoleName(), role);
                        }
                        Collections.sort(allRoleNames);

                        roleNameCombo.removeAll();
                        for (String roleName : allRoleNames) {
                            roleNameCombo.add(roleName);

                            Role role = allRolesMap.get(roleName);
                            roleNameCombo.setData(roleName, role);
                            allRolesSortedByName.add(role);
                        }
                        roleNameCombo.setEnabled(true);

                        if (roleArnToBeSelectedAfterRolesAreLoaded != null) {
                            doSelectRoleByArn(roleArnToBeSelectedAfterRolesAreLoaded);
                            roleArnToBeSelectedAfterRolesAreLoaded = null;
                        } else {
                            roleNameCombo.select(0);
                        }

                        roleLoadedObservable.setValue(true);
                        onRoleSelectionChange();
                    }

                    createRoleButton.setEnabled(true);

                } catch (Exception e) {
                    LambdaPlugin.getDefault().reportException("Failed to load IAM roles.", e);
                }
            }
        });
    }

    private void onRoleSelectionChange() {
        if (roleLoadedObservable.getValue().equals(Boolean.TRUE)) {
            Role role = (Role) roleNameCombo.getData(roleNameCombo.getText());
            dataModel.getFunctionConfigPageDataModel().setRole(role);
        }
    }

    private static Label newLabel(Composite parent, String text) {
        Label label = new Label(parent, SWT.NONE);
        label.setText(text);
        return label;
    }

    /*
     * Italic font resource
     */

    private Font italicFont;

    private void setItalicFont(Control control) {
        FontData[] fontData = control.getFont().getFontData();
        for (FontData fd : fontData) {
            fd.setStyle(SWT.ITALIC);
        }
        italicFont = new Font(Display.getDefault(), fontData);
        control.setFont(italicFont);
    }

    @Override
    public void dispose() {
        if (italicFont != null)
            italicFont.dispose();
        super.dispose();
    }

    /*
     * Analytics
     */

    private void trackFunctionHandlerComboSelectionChange() {
        ToolkitAnalyticsManager analytics = AwsToolkitCore.getDefault().getAnalyticsManager();
        analytics.publishEvent(analytics.eventBuilder().setEventType(EVENT_TYPE_UPLOAD_FUNCTION_WIZARD)
                .addAttribute(ATTR_NAME_CHANGE_SELECTION, ATTR_VALUE_FUNCTION_HANDLER_SELECTION_COMBO).build());
    }

    private void trackRoleComboSelectionChange() {
        ToolkitAnalyticsManager analytics = AwsToolkitCore.getDefault().getAnalyticsManager();
        analytics.publishEvent(analytics.eventBuilder().setEventType(EVENT_TYPE_UPLOAD_FUNCTION_WIZARD)
                .addAttribute(ATTR_NAME_CHANGE_SELECTION, ATTR_VALUE_IAM_ROLE_SELECTION_COMBO).build());
    }

    private void trackS3BucketComboSelectionChange() {
        ToolkitAnalyticsManager analytics = AwsToolkitCore.getDefault().getAnalyticsManager();
        analytics.publishEvent(analytics.eventBuilder().setEventType(EVENT_TYPE_UPLOAD_FUNCTION_WIZARD)
                .addAttribute(ATTR_NAME_CHANGE_SELECTION, ATTR_VALUE_S3_BUCKET_SELECTION_COMBO).build());
    }

    private void trackClickCreateNewRoleButton() {
        ToolkitAnalyticsManager analytics = AwsToolkitCore.getDefault().getAnalyticsManager();
        analytics.publishEvent(analytics.eventBuilder().setEventType(EVENT_TYPE_UPLOAD_FUNCTION_WIZARD)
                .addAttribute(ATTR_NAME_CLICK, ATTR_VALUE_CREATE_IAM_ROLE_BUTTON).build());
    }

    private void trackClickCreateNewBucketButton() {
        ToolkitAnalyticsManager analytics = AwsToolkitCore.getDefault().getAnalyticsManager();
        analytics.publishEvent(analytics.eventBuilder().setEventType(EVENT_TYPE_UPLOAD_FUNCTION_WIZARD)
                .addAttribute(ATTR_NAME_CLICK, ATTR_VALUE_CREATE_S3_BUCKET_BUTTON).build());
    }

    private void trackLoadRoleTimeDuration(long duration) {
        ToolkitAnalyticsManager analytics = AwsToolkitCore.getDefault().getAnalyticsManager();
        analytics.publishEvent(analytics.eventBuilder().setEventType(EVENT_TYPE_UPLOAD_FUNCTION_WIZARD)
                .addMetric(METRIC_NAME_LOAD_IAM_ROLE_TIME_DURATION_MS, duration).build());
    }

    private void trackLoadBucketTimeDuration(long duration) {
        ToolkitAnalyticsManager analytics = AwsToolkitCore.getDefault().getAnalyticsManager();
        analytics.publishEvent(analytics.eventBuilder().setEventType(EVENT_TYPE_UPLOAD_FUNCTION_WIZARD)
                .addMetric(METRIC_NAME_LOAD_S3_BUCKET_TIME_DURATION_MS, (double) duration).build());
    }
}