com.amazonaws.eclipse.core.ui.preferences.AwsAccountPreferencePageTab.java Source code

Java tutorial

Introduction

Here is the source code for com.amazonaws.eclipse.core.ui.preferences.AwsAccountPreferencePageTab.java

Source

/*
 * Copyright 2008-2014 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.core.ui.preferences;

import java.io.File;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.UUID;

import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.preference.BooleanFieldEditor;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.ComboViewer;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
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.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.TabFolder;
import org.eclipse.swt.widgets.TabItem;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.forms.events.ExpansionEvent;
import org.eclipse.ui.forms.events.IExpansionListener;
import org.eclipse.ui.forms.widgets.ExpandableComposite;
import org.eclipse.ui.forms.widgets.Section;

import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.auth.profile.internal.Profile;
import com.amazonaws.eclipse.core.AccountInfo;
import com.amazonaws.eclipse.core.AwsToolkitCore;
import com.amazonaws.eclipse.core.AwsUrls;
import com.amazonaws.eclipse.core.accounts.AccountInfoImpl;
import com.amazonaws.eclipse.core.accounts.preferences.PluginPreferenceStoreAccountOptionalConfiguration;
import com.amazonaws.eclipse.core.accounts.profiles.SdkProfilesCredentialsConfiguration;
import com.amazonaws.eclipse.core.preferences.PreferenceConstants;
import com.amazonaws.eclipse.core.regions.Region;
import com.amazonaws.eclipse.core.ui.WebLinkListener;
import com.amazonaws.eclipse.core.ui.preferences.accounts.AccountInfoPropertyEditor;
import com.amazonaws.eclipse.core.ui.preferences.accounts.AccountInfoPropertyEditorFactory;
import com.amazonaws.eclipse.core.ui.preferences.accounts.AccountInfoPropertyEditorFactory.AccountInfoFilePropertyEditor;
import com.amazonaws.eclipse.core.ui.preferences.accounts.AccountInfoPropertyEditorFactory.AccountInfoStringPropertyEditor;
import com.amazonaws.eclipse.core.ui.preferences.accounts.AccountInfoPropertyEditorFactory.PropertyType;

/**
 * A tab item contained in the AWS account preference page. Each tab item
 * corresponds to a set of region-specific (or global) accounts.
 */
public class AwsAccountPreferencePageTab extends TabItem {

    /** The preference store persisted by this tab */
    private final IPreferenceStore prefStore;

    /**
     * The preference page to which the validation error message should be
     * reported.
     */
    private final AwsAccountPreferencePage parentPrefPage;

    /**
     * The region associated with this tab. Null if this is the tab for global
     * account
     */
    private final Region region;

    /**
     * The identifier of the current default account for the region represented
     * by this tab
     */
    private String currentRegionAccountId;

    /**
     * @see AwsAccountPreferencePage#getAccountInfoByIdentifier()
     */
    private final LinkedHashMap<String, AccountInfo> accountInfoByIdentifier;

    /**
     * @see AwsAccountPreferencePage#getAccountInfoToBeDeleted()
     */
    private final Set<AccountInfo> accountInfoToBeDeleted;

    /**
     * The DataBindingContext instance shared by all the account info property
     * editors
     */
    private final DataBindingContext dataBindingContext = new DataBindingContext();

    /**
     * Additional listeners to be notified when the account infomation is modified
     */
    private final List<ModifyListener> accountInfoFieldEditorListeners = new LinkedList<ModifyListener>();

    /** Page controls */
    BooleanFieldEditor enableRegionDefaultAccount;
    private Composite accountInfoSection;
    private ComboViewer accountSelector;
    private Button deleteAccount;
    private Label defaultAccountExplanationLabel;
    private AccountInfoPropertyEditor accountNameFieldEditor;
    private AccountInfoPropertyEditor userIdFieldEditor;
    private AccountInfoPropertyEditor accessKeyFieldEditor;
    private AccountInfoPropertyEditor secretKeyFieldEditor;
    private AccountInfoPropertyEditor certificateFieldEditor;
    private AccountInfoPropertyEditor certificatePrivateKeyFieldEditor;

    /**
     * The set of field editors that need to know when the account or its name
     * changes.
     */
    private final Collection<AccountInfoPropertyEditor> accountFieldEditors = new LinkedList<AccountInfoPropertyEditor>();

    /** The checkbox controlling how we display the secret key */
    private Button hideSecretKeyCheckbox;

    /** The Text control in the secret key field editor */
    private Text secretKeyText;

    public AwsAccountPreferencePageTab(TabFolder tabFolder, int tabIndex, AwsAccountPreferencePage parentPrefPage,
            Region region) {
        this(tabFolder, tabIndex, parentPrefPage, region, AwsToolkitCore.getDefault().getPreferenceStore());
    }

    /**
     * Construct a new tab in the given TabFolder at the given index, relating
     * to the given region.
     *
     * @param tabFolder
     *            TabFolder where this tab is inserted.
     * @param tabIndex
     *            The index where this tab should be inserted.
     * @param parentPrefPage
     *            The parent account preference page which contains this tab.
     * @param region
     *            The region which this page tab manages
     * @param prefStore
     *            The preference store that this tab persists with.
     */
    public AwsAccountPreferencePageTab(TabFolder tabFolder, int tabIndex, AwsAccountPreferencePage parentPrefPage,
            Region region, IPreferenceStore prefStore) {
        super(tabFolder, SWT.NONE, tabIndex);
        this.parentPrefPage = parentPrefPage;
        this.accountInfoByIdentifier = parentPrefPage.getAccountInfoByIdentifier();
        this.accountInfoToBeDeleted = parentPrefPage.getAccountInfoToBeDeleted();
        this.region = region;
        this.prefStore = prefStore;

        if (region == null) {
            setText("Global Configuration");
        } else {
            // Use the region name as the tab title
            setText(region.getName());
        }

        loadCurrentDefaultAccountId();

        writeDefaultPreferenceValues();

        final Composite composite = setUpCompositeLayout();

        // Not strictly necessary, since AwsAccountPreferencePage will never
        // construct this class with an empty map of accounts.
        if (!accountInfoByIdentifier.isEmpty()) {
            addControls(composite);
        } else {
            addCredentialsFileLoadingFailureLabel(composite);
        }

        setControl(composite);
    }

    /*
     * Public interfaces
     */

    public void loadDefault() {
        if (enableRegionDefaultAccount != null) {
            enableRegionDefaultAccount.loadDefault();
        }
    }

    public void doStore() {
        // Store the check box on whether region-default-account is enabled
        if (enableRegionDefaultAccount != null) {
            enableRegionDefaultAccount.store();
        }

        // Save the id of the current default region account
        getPreferenceStore().setValue(getRegionCurrentAccountPrefKey(), currentRegionAccountId);

        // Refresh the UI of the account selector
        refreshAccountSelectorUI();
    }

    public Region getRegion() {
        return region;
    }

    public boolean isGlobalAccoutTab() {
        return region == null;
    }

    /**
     * Returns names of all the accounts.
     */
    public List<String> getAccountNames() {
        List<String> accountNames = new LinkedList<String>();
        for (AccountInfo account : accountInfoByIdentifier.values()) {
            accountNames.add(account.getAccountName());
        }
        return accountNames;
    }

    /**
     * Check for duplicate account names in this tab.
     *
     * @return True if duplicate account names are found in this tab.
     */
    public boolean checkDuplicateAccountName() {
        Set<String> accountNames = new HashSet<String>();
        for (String accountName : getAccountNames()) {
            if (!accountNames.add(accountName)) {
                return true;
            }
        }
        return false;
    }

    synchronized void addAccountInfoFieldEditorModifyListener(ModifyListener listener) {
        accountInfoFieldEditorListeners.add(listener);
    }

    /*
     * Private Interface
     */

    private void loadCurrentDefaultAccountId() {
        currentRegionAccountId = getPreferenceStore().getString(getRegionCurrentAccountPrefKey());
    }

    private void writeDefaultPreferenceValues() {
        if (!isGlobalAccoutTab()) {
            getPreferenceStore().setDefault(getRegionAccountEnabledPrefKey(), true);
        }
    }

    private Composite setUpCompositeLayout() {
        Composite composite = new Composite(getParent(), SWT.NONE);
        composite.setLayout(new GridLayout(3, false));
        GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
        composite.setLayoutData(gridData);
        return composite;
    }

    private void addCredentialsFileLoadingFailureLabel(Composite composite) {
        new Label(composite, SWT.READ_ONLY).setText(String.format(
                "Failed to load credential profiles from (%s).%n"
                        + "Please check that your credentials file is in the correct format.",
                prefStore.getString(PreferenceConstants.P_CREDENTIAL_PROFILE_FILE_LOCATION)));
    }

    private void addControls(final Composite composite) {
        // If it's regional tab, add the enable-region-default-account check-box
        // and the remove-this-tab button
        if (!isGlobalAccoutTab()) {
            addEnableRegionDefaultControls(composite);
        }

        // The main account info section
        accountInfoSection = createAccountInfoSection(composite);

        // Set up the change listener on the enable-region-default-account
        // check-box, so that the account info section is grayed out whenever
        // it's unchecked.
        setUpEnableRegionDefaultChangeListener();

        AwsToolkitPreferencePage.tweakLayout((GridLayout) composite.getLayout());
    }

    /**
     * Add the enable-region-default-account check-box and the remove-this-tab
     * button
     */
    private void addEnableRegionDefaultControls(Composite parent) {
        enableRegionDefaultAccount = new BooleanFieldEditor(getRegionAccountEnabledPrefKey(),
                "Enable region default account for " + region.getName(), parent);
        enableRegionDefaultAccount.setPreferenceStore(getPreferenceStore());

        Button removeTabButton = new Button(parent, SWT.PUSH);
        removeTabButton.setText("Remove");
        removeTabButton.setToolTipText("Remove default account configuration for this region.");
        removeTabButton.setImage(AwsToolkitCore.getDefault().getImageRegistry().get(AwsToolkitCore.IMAGE_REMOVE));
        removeTabButton.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false));
        removeTabButton.addSelectionListener(new SelectionAdapter() {

            public void widgetSelected(SelectionEvent e) {
                MessageDialog confirmRemoveTabDialog = new MessageDialog(Display.getDefault().getActiveShell(),
                        "Remove all accounts for " + region.getName(),
                        AwsToolkitCore.getDefault().getImageRegistry().get(AwsToolkitCore.IMAGE_AWS_ICON),
                        "Are you sure you want to remove all the configured accounts for " + region.getName() + "?",
                        MessageDialog.CONFIRM, new String[] { "Cancel", "OK" }, 1);
                if (confirmRemoveTabDialog.open() == 1) {
                    AwsAccountPreferencePageTab.this.dispose();
                }

            }
        });
    }

    /**
     * Creates the whole section of account info
     */
    private Composite createAccountInfoSection(Composite parent) {
        Composite accountInfoSection = new Composite(parent, SWT.NONE);
        accountInfoSection.setLayout(new GridLayout());
        GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
        gridData.horizontalSpan = 3;
        accountInfoSection.setLayoutData(gridData);
        createAccountSelector(accountInfoSection);

        WebLinkListener webLinkListener = new WebLinkListener();
        Group awsGroup = createAccountDetailSectionGroup(accountInfoSection, webLinkListener);
        createOptionalSection(awsGroup, webLinkListener);

        return accountInfoSection;
    }

    /**
     * Creates the account selection section.
     */
    private Composite createAccountSelector(final Composite parent) {
        Composite composite = new Composite(parent, SWT.NONE);
        composite.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
        composite.setLayout(new GridLayout(4, false));

        new Label(composite, SWT.READ_ONLY).setText("Default Profile: ");

        accountSelector = new ComboViewer(composite, SWT.DROP_DOWN | SWT.READ_ONLY);
        accountSelector.getCombo().setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));

        // Use a List of AccountInfo objects as the data input for the combo
        // viewer
        accountSelector.setContentProvider(ArrayContentProvider.getInstance());
        accountSelector.setLabelProvider(new LabelProvider() {
            @Override
            public String getText(Object element) {
                if (element instanceof AccountInfo) {
                    AccountInfo account = (AccountInfo) element;
                    if (account.isDirty()) {
                        return "*" + account.getAccountName();
                    } else {
                        return account.getAccountName();
                    }
                }
                return super.getText(element);
            }
        });

        AccountInfo currentRegionAccount = accountInfoByIdentifier.get(currentRegionAccountId);
        // In some of the edge-cases, currentRegionAccount could be null.
        // e.g. a specific credential profile account is removed externally, but
        // the data in the preference store is not yet updated.
        if (currentRegionAccount == null) {
            currentRegionAccount = accountInfoByIdentifier.values().iterator().next();
            currentRegionAccountId = currentRegionAccount.getInternalAccountId();
        }

        final List<AccountInfo> allAccounts = new LinkedList<AccountInfo>(accountInfoByIdentifier.values());

        setUpAccountSelectorItems(allAccounts, currentRegionAccount);

        // Add selection listener to the account selector, so that all the
        // account info editors are notified of the newly selected AccountInfo
        // object.
        accountSelector.addSelectionChangedListener(new ISelectionChangedListener() {

            public void selectionChanged(SelectionChangedEvent event) {
                IStructuredSelection selection = (IStructuredSelection) event.getSelection();
                Object selectedObject = selection.getFirstElement();

                if (selectedObject instanceof AccountInfo) {
                    AccountInfo accountInfo = (AccountInfo) selectedObject;
                    accountChanged(accountInfo);
                }
            }

        });

        final Button addNewAccount = new Button(composite, SWT.PUSH);
        addNewAccount.setText("Add profile");
        deleteAccount = new Button(composite, SWT.PUSH);
        deleteAccount.setText("Remove profile");
        deleteAccount.setEnabled(allAccounts.size() > 1);

        defaultAccountExplanationLabel = new Label(composite, SWT.WRAP);
        defaultAccountExplanationLabel.setText(getDefaultAccountExplanationText());

        parentPrefPage.setItalicFont(defaultAccountExplanationLabel);

        GridData layoutData = new GridData(SWT.FILL, SWT.TOP, true, false);
        layoutData.horizontalSpan = 4;
        layoutData.widthHint = 200;
        defaultAccountExplanationLabel.setLayoutData(layoutData);

        addNewAccount.addSelectionListener(new SelectionAdapter() {

            @Override
            public void widgetSelected(SelectionEvent e) {
                String newAccountId = UUID.randomUUID().toString();
                AccountInfo newAccountInfo = createNewProfileAccountInfo(newAccountId);

                String newAccountName = region == null ? "New Profile" : "New " + region.getName() + " Profile";
                newAccountInfo.setAccountName(newAccountName); // this will mark the AccountInfo object dirty

                accountInfoByIdentifier.put(newAccountId, newAccountInfo);

                setUpAccountSelectorItems(accountInfoByIdentifier.values(), newAccountInfo);

                for (AwsAccountPreferencePageTab tab : parentPrefPage.getAllAccountPreferencePageTabs()) {
                    if (tab != AwsAccountPreferencePageTab.this) {
                        tab.refreshAccountSelectorItems();
                    }
                }

                parentPrefPage.updatePageValidationOfAllTabs();
            }
        });

        deleteAccount.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                accountInfoToBeDeleted.add(accountInfoByIdentifier.get(currentRegionAccountId));
                accountInfoByIdentifier.remove(currentRegionAccountId);

                // If all the accounts are deleted, create a temporary
                // AccountInfo object
                if (accountInfoByIdentifier.isEmpty()) {
                    String newAccountId = UUID.randomUUID().toString();
                    AccountInfo newAccountInfo = createNewProfileAccountInfo(newAccountId);

                    // Account name : default-region-id
                    newAccountInfo.setAccountName(getRegionAccountDefaultName());
                    accountInfoByIdentifier.put(newAccountId, newAccountInfo);
                }

                // Use the first AccountInfo as the next selected account
                AccountInfo nextDefaultAccount = accountInfoByIdentifier.values().iterator().next();

                setUpAccountSelectorItems(accountInfoByIdentifier.values(), nextDefaultAccount);

                for (AwsAccountPreferencePageTab tab : parentPrefPage.getAllAccountPreferencePageTabs()) {
                    if (tab != AwsAccountPreferencePageTab.this) {
                        tab.refreshAccountSelectorItems();
                    }
                }

                parentPrefPage.updatePageValidationOfAllTabs();
            }
        });

        accountSelector.getCombo().addModifyListener(new ModifyListener() {

            public void modifyText(ModifyEvent arg0) {
                if (accountSelector.getCombo().getItemCount() > 1) {
                    deleteAccount.setEnabled(true);
                } else {
                    deleteAccount.setEnabled(false);
                }

            }
        });

        return composite;
    }

    /**
     * Refreshes the UI of the account selector.
     */
    private void refreshAccountSelectorUI() {
        accountSelector.refresh();
    }

    /**
     * Refreshes the input data for the account selector combo and keep the current
     * selection.
     */
    private void refreshAccountSelectorItems() {
        setUpAccountSelectorItems(accountInfoByIdentifier.values(),
                accountInfoByIdentifier.get(currentRegionAccountId));
    }

    /**
     * Set the input data for the account selector combo and also set the
     * selection to the specified AccountInfo object.
     */
    private void setUpAccountSelectorItems(Collection<AccountInfo> allAccounts, AccountInfo selectedAccount) {
        accountSelector.setInput(allAccounts);

        // If the given account is not found, then select the first element
        if (!allAccounts.contains(selectedAccount)) {
            selectedAccount = allAccounts.iterator().next();
        }

        accountSelector.setSelection(new StructuredSelection(selectedAccount), true); // visible=true

        // TODO: copied from the existing code, not sure why it's necessary
        accountSelector.getCombo().getParent().getParent().layout();
    }

    /**
     * Creates the widgets for the AWS account information section on this
     * preference page.
     *
     * @param parent
     *            The parent preference page composite.
     * @param webLinkListener
     *            The listener to attach to links.
     */
    private Group createAccountDetailSectionGroup(final Composite parent, WebLinkListener webLinkListener) {

        final Group awsAccountGroup = new Group(parent, SWT.NONE);
        GridData gridData1 = new GridData(SWT.FILL, SWT.TOP, true, false);
        gridData1.horizontalSpan = 4;
        gridData1.verticalIndent = 10;
        awsAccountGroup.setLayoutData(gridData1);
        awsAccountGroup.setText("Profile Details:");

        if (isGlobalAccoutTab()) {
            String linkText = "<a href=\"" + AwsUrls.SIGN_UP_URL + "\">Sign up for a new AWS account</a> or "
                    + "<a href=\"" + AwsUrls.SECURITY_CREDENTIALS_URL
                    + "\">manage your existing AWS security credentials</a>.";
            AwsToolkitPreferencePage.newLink(webLinkListener, linkText, awsAccountGroup);
            AwsToolkitPreferencePage.createSpacer(awsAccountGroup);
        }

        accountNameFieldEditor = newStringFieldEditor(accountInfoByIdentifier.get(currentRegionAccountId),
                "accountName", "&Profile Name:", awsAccountGroup);
        accessKeyFieldEditor = newStringFieldEditor(accountInfoByIdentifier.get(currentRegionAccountId),
                "accessKey", "&Access Key ID:", awsAccountGroup);
        secretKeyFieldEditor = newStringFieldEditor(accountInfoByIdentifier.get(currentRegionAccountId),
                "secretKey", "&Secret Access Key:", awsAccountGroup);

        // create an empty label in the first column so that the hide secret key
        // checkbox lines up with the other text controls
        new Label(awsAccountGroup, SWT.NONE);

        hideSecretKeyCheckbox = new Button(awsAccountGroup, SWT.CHECK);
        hideSecretKeyCheckbox.setText("Show secret access key");
        hideSecretKeyCheckbox.setSelection(false);
        GridData gridData = new GridData(GridData.FILL_HORIZONTAL);
        gridData.horizontalSpan = 2;
        gridData.verticalIndent = -6;
        gridData.horizontalIndent = 3;
        hideSecretKeyCheckbox.setLayoutData(gridData);
        hideSecretKeyCheckbox.addListener(SWT.Selection, new Listener() {

            public void handleEvent(Event event) {
                updateSecretKeyText();
            }
        });

        secretKeyText = secretKeyFieldEditor.getTextControl();

        updateSecretKeyText();
        AwsToolkitPreferencePage.tweakLayout((GridLayout) awsAccountGroup.getLayout());

        accountFieldEditors.add(accountNameFieldEditor);
        accountFieldEditors.add(accessKeyFieldEditor);
        accountFieldEditors.add(secretKeyFieldEditor);

        return awsAccountGroup;
    }

    /**
     * Creates the widgets for the optional configuration section on this
     * preference page.
     *
     * @param parent
     *            The parent preference page composite.
     * @param webLinkListener
     *            The listener to attach to links.
     */
    private void createOptionalSection(final Composite parent, WebLinkListener webLinkListener) {

        Section expandableComposite = new Section(parent, ExpandableComposite.TWISTIE);
        GridData gd = new GridData(SWT.FILL, SWT.TOP, true, false);
        gd.horizontalSpan = AwsToolkitPreferencePage.LAYOUT_COLUMN_WIDTH;
        expandableComposite.setLayoutData(gd);

        Composite optionalConfigGroup = new Composite(expandableComposite, SWT.NONE);
        optionalConfigGroup.setLayout(new GridLayout());
        optionalConfigGroup.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));

        expandableComposite.setClient(optionalConfigGroup);
        expandableComposite.setText("Optional configuration:");
        expandableComposite.setExpanded(false);

        expandableComposite.addExpansionListener(new IExpansionListener() {

            public void expansionStateChanging(ExpansionEvent e) {
            }

            public void expansionStateChanged(ExpansionEvent e) {
                parent.getParent().layout();
            }
        });

        String linkText = "Your AWS account number and X.509 certificate are only needed if you want to bundle EC2 instances from Eclipse.  "
                + "<a href=\"" + AwsUrls.SECURITY_CREDENTIALS_URL + "\">Manage your AWS X.509 certificate</a>.";
        AwsToolkitPreferencePage.newLink(webLinkListener, linkText, optionalConfigGroup);

        AwsToolkitPreferencePage.createSpacer(optionalConfigGroup);

        userIdFieldEditor = newStringFieldEditor(accountInfoByIdentifier.get(currentRegionAccountId), "userId",
                "AWS Account &Number:", optionalConfigGroup);
        createFieldExampleLabel(optionalConfigGroup, "ex: 1111-2222-3333");

        certificateFieldEditor = newFileFieldEditor(accountInfoByIdentifier.get(currentRegionAccountId),
                "ec2CertificateFile", "&Certificate File:", optionalConfigGroup);
        certificatePrivateKeyFieldEditor = newFileFieldEditor(accountInfoByIdentifier.get(currentRegionAccountId),
                "ec2PrivateKeyFile", "&Private Key File:", optionalConfigGroup);

        AwsToolkitPreferencePage.tweakLayout((GridLayout) optionalConfigGroup.getLayout());

        accountFieldEditors.add(userIdFieldEditor);
        accountFieldEditors.add(certificateFieldEditor);
        accountFieldEditors.add(certificatePrivateKeyFieldEditor);
    }

    /**
     * Set up the change listener on the enable-region-default-account
     * check-box, so that the account info section is grayed out whenever it's
     * unchecked.
     */
    private void setUpEnableRegionDefaultChangeListener() {
        if (enableRegionDefaultAccount != null) {
            enableRegionDefaultAccount.setPropertyChangeListener(new IPropertyChangeListener() {

                public void propertyChange(PropertyChangeEvent event) {
                    boolean enabled = (Boolean) event.getNewValue();
                    AwsAccountPreferencePageTab.this.toggleAccountInfoSectionEnabled(enabled);
                }
            });

            enableRegionDefaultAccount.load();
            toggleAccountInfoSectionEnabled(enableRegionDefaultAccount.getBooleanValue());
        }
    }

    /**
     * Creates a new label to serve as an example for a field, using the
     * specified text. The label will be displayed with a subtle font. This
     * method assumes that the grid layout for the specified composite contains
     * three columns.
     *
     * @param composite
     *            The parent component for this new widget.
     * @param text
     *            The example text to display in the new label.
     */
    private void createFieldExampleLabel(Composite composite, String text) {
        Label label = new Label(composite, SWT.NONE);
        Font font = label.getFont();

        label = new Label(composite, SWT.NONE);
        label.setText(text);

        FontData[] fontData = font.getFontData();
        if (fontData.length > 0) {
            FontData fd = fontData[0];
            fd.setHeight(10);
            fd.setStyle(SWT.ITALIC);

            label.setFont(new Font(Display.getCurrent(), fd));
        }

        GridData gridData = new GridData(GridData.FILL_HORIZONTAL);
        gridData.horizontalSpan = 2;
        gridData.verticalAlignment = SWT.TOP;
        gridData.horizontalIndent = 3;
        gridData.verticalIndent = -4;
        label.setLayoutData(gridData);
    }

    /**
     * Invoked whenever the selected account information changes.
     */
    private void accountChanged(AccountInfo accountInfo) {
        currentRegionAccountId = accountInfo.getInternalAccountId();
        for (AccountInfoPropertyEditor editor : accountFieldEditors) {
            editor.accountChanged(accountInfo);
        }
    }

    /**
     * Update or clear the error message and validity of the page.
     *
     * @return True if the page is invalid.
     */
    boolean updatePageValidation() {
        String errorString = validateFieldValues();
        if (errorString != null) {
            parentPrefPage.setValid(false);
            parentPrefPage.setErrorMessage(errorString);
            return true;
        }

        if (checkDuplicateAccountName()) {
            parentPrefPage.setValid(false);
            parentPrefPage.setErrorMessage("Duplicate account name defined");
            return true;
        }

        return false;
    }

    /**
     * Returns an error message if there's a problem with the page's fields, or
     * null if there are no errors.
     */
    private String validateFieldValues() {
        for (AccountInfo accountInfo : accountInfoByIdentifier.values()) {
            if (accountInfo.getAccountName().trim().isEmpty()) {
                return "Account name must not be blank";
            }

            if (invalidFile(accountInfo.getEc2CertificateFile())) {
                return "Certificate file does not exist";
            }

            if (invalidFile(accountInfo.getEc2PrivateKeyFile())) {
                return "Private key file does not exist";
            }
        }

        return null;
    }

    private boolean invalidFile(String certFile) {
        return certFile.trim().length() > 0 && !new File(certFile).exists();
    }

    /**
     * Updates the secret key text according to whether or not the
     * "display secret key in plain text" checkbox is selected or not.
     */
    private void updateSecretKeyText() {
        if (hideSecretKeyCheckbox == null)
            return;
        if (secretKeyText == null)
            return;

        if (hideSecretKeyCheckbox.getSelection()) {
            secretKeyText.setEchoChar('\0');
        } else {
            secretKeyText.setEchoChar('*');
        }
    }

    private AccountInfoPropertyEditor newStringFieldEditor(AccountInfo currentAccount, String propertyName,
            String label, Composite parent) {
        AccountInfoPropertyEditor fieldEditor = AccountInfoPropertyEditorFactory.getAccountInfoPropertyEditor(
                currentAccount, propertyName, PropertyType.STRING_PROPERTY, label, parent, dataBindingContext);
        setUpFieldEditor(fieldEditor, parent);
        return fieldEditor;
    }

    private AccountInfoPropertyEditor newFileFieldEditor(AccountInfo currentAccount, String propertyName,
            String label, Composite parent) {
        AccountInfoPropertyEditor fieldEditor = AccountInfoPropertyEditorFactory.getAccountInfoPropertyEditor(
                currentAccount, propertyName, PropertyType.FILE_PROPERTY, label, parent, dataBindingContext);
        setUpFieldEditor(fieldEditor, parent);
        return fieldEditor;
    }

    protected void setUpFieldEditor(final AccountInfoPropertyEditor fieldEditor, Composite parent) {
        if (fieldEditor instanceof AccountInfoStringPropertyEditor) {
            ((AccountInfoStringPropertyEditor) fieldEditor).getStringFieldEditor().fillIntoGrid(parent,
                    AwsToolkitPreferencePage.LAYOUT_COLUMN_WIDTH);
        } else if (fieldEditor instanceof AccountInfoFilePropertyEditor) {
            ((AccountInfoFilePropertyEditor) fieldEditor).getFileFieldEditor().fillIntoGrid(parent,
                    AwsToolkitPreferencePage.LAYOUT_COLUMN_WIDTH);
        }

        // Validate the page whenever the editors are touched
        fieldEditor.getTextControl().addModifyListener(new ModifyListener() {
            public void modifyText(ModifyEvent e) {
                /*
                 * Since the data-binding also observes the Modify event on the
                 * text control, there is an edge case where the AccountInfo
                 * model might not have been updated when the modifyText
                 * callbacked is triggered. To make sure the updated data model
                 * is visible to the subsequent operations, we forcefully push
                 * the data from the editor to the data model.
                 */
                fieldEditor.forceUpdateEditorValueToAccountInfoModel();

                /*
                 * Now that the property value change has been applied to the
                 * AccountInfo object. Since the accountSelector is backed by
                 * the collection of the same AccountInfo objects, we can simply
                 * refresh the UI of accountSelector; all the account name
                 * changes will be reflected.
                 */
                for (AwsAccountPreferencePageTab tab : parentPrefPage.getAllAccountPreferencePageTabs()) {
                    tab.refreshAccountSelectorUI();
                }

                parentPrefPage.updatePageValidationOfAllTabs();

                for (ModifyListener listener : accountInfoFieldEditorListeners) {
                    listener.modifyText(null);
                }
            }
        });
    }

    private String getRegionCurrentAccountPrefKey() {
        return PreferenceConstants.P_REGION_CURRENT_DEFAULT_ACCOUNT(region);
    }

    private String getRegionAccountDefaultName() {
        return PreferenceConstants.DEFAULT_ACCOUNT_NAME(region);
    }

    private String getRegionAccountEnabledPrefKey() {
        return PreferenceConstants.P_REGION_DEFAULT_ACCOUNT_ENABLED(region);
    }

    private IPreferenceStore getPreferenceStore() {
        return prefStore;
    }

    /**
     * Recursively enable/disable all the children controls of account-info
     * section.
     */
    private void toggleAccountInfoSectionEnabled(boolean enabled) {
        AwsAccountPreferencePage.setEnabledOnAllChildern(accountInfoSection, enabled);
    }

    private AccountInfo createNewProfileAccountInfo(String newAccountId) {
        Profile emptyProfile = new Profile("", new BasicAWSCredentials("", ""));
        return new AccountInfoImpl(newAccountId,
                new SdkProfilesCredentialsConfiguration(prefStore, newAccountId, emptyProfile),
                new PluginPreferenceStoreAccountOptionalConfiguration(prefStore, newAccountId));
    }

    private String getDefaultAccountExplanationText() {
        if (isGlobalAccoutTab()) {
            return "This credential profile will be used by default to access all AWS regions "
                    + "that are not configured with a region-specific account.";
        } else {
            return "This credential profile will be used by default to access AWS resources in " + region.getName()
                    + "(" + region.getId() + ") region.";
        }
    }

    /**
     * For debug purpose only.
     */
    @SuppressWarnings("unused")
    private void printAccounts() {
        System.out.println(
                "*** Default accounts(" + accountInfoByIdentifier.size() + ") tab for " + this.getText() + " ***");
        for (String id : accountInfoByIdentifier.keySet()) {
            System.out.println("    " + accountInfoByIdentifier.get(id) + "(" + id + ")");
        }
        System.out.println("Current default: " + accountInfoByIdentifier.get(currentRegionAccountId));
    }

    /**
     * Override this method in order to let SWT allow sub-classing TabItem
     */
    @Override
    public void checkSubclass() {
        // no-op
    }
}