/*
* 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.accounts;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;
import org.apache.http.annotation.NotThreadSafe;
import org.eclipse.jface.preference.IPreferenceStore;
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.accounts.preferences.PluginPreferenceStoreAccountOptionalConfiguration;
import com.amazonaws.eclipse.core.accounts.profiles.SdkCredentialsFileMonitor;
import com.amazonaws.eclipse.core.accounts.profiles.SdkProfilesCredentialsConfiguration;
import com.amazonaws.eclipse.core.accounts.profiles.SdkProfilesFactory;
import com.amazonaws.eclipse.core.preferences.PreferenceConstants;
import com.amazonaws.eclipse.core.preferences.PreferencePropertyChangeListener;
import com.amazonaws.eclipse.core.regions.Region;
import com.amazonaws.eclipse.core.regions.RegionUtils;
import com.amazonaws.eclipse.core.ui.preferences.AwsAccountPreferencePage;
/**
* This class acts as a facade for all the account-related configurations for
* the plugin. Different feature components should use this class to query/set
* the current default account. It is also responsible to notify the registered
* listeners about the change of default account.
* <p>
* When requested to retrieve a specific account info, this class delegates to a
* list of AccountInfoProvider implementations to aggregate all the accounts
* configured via different ways (e.g. by the Eclipse preference store system,
* or loaded from the local credentials file).
*/
@NotThreadSafe
public final class AwsPluginAccountManager {
/**
* The preference store where the configuration for the global/regional
* default account is persisted..
*/
private final IPreferenceStore preferenceStore;
/** Monitors for changes of global/regional default account preference */
private DefaultAccountMonitor defaultAccountMonitor;
/** Monitors the configured location of the credentials file as specified in the preference store */
private final SdkCredentialsFileMonitor sdkCredentialsFileMonitor;
/**
* The AccountInfoProvider from which the manager retrieves the AccountInfo
* objects.
*/
private final AccountInfoProvider accountInfoProvider;
/**
* The AccountInfo object to return when no account is configured and the
* toolkit is unable to bootstrap the credentials file.
*/
private final AccountInfo tempAccount;
private boolean noAccountConfigured = false;
public AwsPluginAccountManager(IPreferenceStore preferenceStore,
AccountInfoProvider accountInfoProvider) {
this.preferenceStore = preferenceStore;
this.accountInfoProvider = accountInfoProvider;
this.sdkCredentialsFileMonitor = new SdkCredentialsFileMonitor();
String accountId = UUID.randomUUID().toString();
tempAccount = new AccountInfoImpl(accountId,
new SdkProfilesCredentialsConfiguration(preferenceStore, accountId,
SdkProfilesFactory.newEmptyBasicProfile("temp")),
new PluginPreferenceStoreAccountOptionalConfiguration(preferenceStore, accountId));
}
/**
* Start all the monitors on account-related preference properties.
*/
public void startAccountMonitors() {
if (defaultAccountMonitor == null) {
defaultAccountMonitor = new DefaultAccountMonitor();
getPreferenceStore().addPropertyChangeListener(
defaultAccountMonitor);
}
}
/**
* Stop all the monitors on account-related preference properties.
*/
public void stopAccountMonitors() {
if (defaultAccountMonitor != null) {
getPreferenceStore().removePropertyChangeListener(defaultAccountMonitor);
}
}
/**
* Start monitoring the location and content of the credentials file
*/
public void startCredentialsFileMonitor() {
sdkCredentialsFileMonitor.start(preferenceStore);
}
/**
* Returns the AccountInfoProvider that is used by this class.
*/
public AccountInfoProvider getAccountInfoProvider() {
return accountInfoProvider;
}
/**
* Returns the currently selected account info. If the current account id is
* not found in the loaded accounts (e.g. when the previously configured
* account is removed externally in the credentials file), this method falls
* back to returning the "default" profile account (or the first profile
* account if the "default" profile doesn't exist). If no account is
* configured in the toolkit (most probably because the toolkit failed to
* load the credentials file), this method returns a temporary empty
* AccountInfo object. In short, this method never returns null.
*
* @return The user's AWS account info.
*/
public AccountInfo getAccountInfo() {
if (noAccountConfigured) {
return tempAccount;
}
AccountInfo currentAccount = getAccountInfo(getCurrentAccountId());
if (currentAccount != null) {
return currentAccount;
}
// Find an existing account to fall back to
Collection<AccountInfo> allAccounts = getAllAccountInfo().values();
if ( !allAccounts.isEmpty() ) {
AwsToolkitCore.getDefault().logInfo("The current accountId is not found in the system. " +
"Switching to the default account.");
AccountInfo fallbackAccount = allAccounts.iterator().next();
// Find the "default" account
for (AccountInfo account : allAccounts) {
if (account.getAccountName().equals(PreferenceConstants.DEFAULT_ACCOUNT_NAME)) {
fallbackAccount = account;
}
}
setCurrentAccountId(fallbackAccount.getInternalAccountId());
return fallbackAccount;
}
AwsToolkitCore.getDefault().logInfo("No account could be found. " +
"Switching to a temporary account.");
// Directly return the temp AccountInfo object if no account could be found
noAccountConfigured = true;
return tempAccount;
}
/**
* Gets account info for the given account name. The query is performed by
* the AccountInfoProvider instance included in this manager. This method
* still checks for the legacy pref-store-based accounts if the account id
* cannot be found in the profile-based accounts.
*
* @param accountId
* The id of the account for which to get info.
*/
public AccountInfo getAccountInfo(String accountId) {
if (accountInfoProvider.getProfileAccountInfo(accountId) != null) {
return accountInfoProvider.getProfileAccountInfo(accountId);
}
if (accountInfoProvider.getLegacyPreferenceStoreAccountInfo(accountId) != null) {
return accountInfoProvider.getLegacyPreferenceStoreAccountInfo(accountId);
}
return null;
}
/**
* Refresh all the account info providers.
*/
public void reloadAccountInfo() {
noAccountConfigured = false;
final boolean noLegacyAccounts = accountInfoProvider.getAllLegacyPreferenceStoreAccontInfo().isEmpty();
// Only bootstrap the credentials file if no legacy account exists
final boolean boostrapCredentialsFile = noLegacyAccounts;
// Only show warning for the credentials file loading failure if no legacy account exists
final boolean showWarningOnFailure = noLegacyAccounts;
accountInfoProvider.refreshProfileAccountInfo(boostrapCredentialsFile, showWarningOnFailure);
}
/**
* Returns the current account Id
*/
public String getCurrentAccountId() {
return getPreferenceStore().getString(
PreferenceConstants.P_CURRENT_ACCOUNT);
}
/**
* Sets the current account id. No error checking is performed, so ensure
* the given account Id is valid.
*/
public void setCurrentAccountId(String accountId) {
getPreferenceStore().setValue(PreferenceConstants.P_CURRENT_ACCOUNT,
accountId);
}
/**
* Update the default account to use according to the current default
* region.
*/
public void updateCurrentAccount() {
updateCurrentAccount(RegionUtils.getCurrentRegion());
}
/**
* Set the given account identifier as the default account for a region. If
* this region does not have any default account setting (or setting is
* disabled), then this method will set it as the global default account.
*/
public void setDefaultAccountId(Region region, String accountId) {
if (AwsAccountPreferencePage.isRegionDefaultAccountEnabled(
getPreferenceStore(), region)) {
getPreferenceStore()
.setValue(
PreferenceConstants
.P_REGION_CURRENT_DEFAULT_ACCOUNT(region),
accountId);
} else {
getPreferenceStore().setValue(
PreferenceConstants.P_GLOBAL_CURRENT_DEFAULT_ACCOUNT,
accountId);
}
}
/**
* Update the current accountId according to the given region.
*/
public void updateCurrentAccount(Region newRegion) {
if (AwsAccountPreferencePage.isRegionDefaultAccountEnabled(
getPreferenceStore(), newRegion)) {
AwsToolkitCore.getDefault().logInfo(
"Switching to region-specific default account for region "
+ newRegion.getId());
// Use the region-specific default account
setCurrentAccountId(getPreferenceStore().getString(
PreferenceConstants
.P_REGION_CURRENT_DEFAULT_ACCOUNT(newRegion)));
} else {
AwsToolkitCore.getDefault().logInfo(
"Switching to global default account");
// Use the global default account
setCurrentAccountId(getPreferenceStore().getString(
PreferenceConstants.P_GLOBAL_CURRENT_DEFAULT_ACCOUNT));
}
}
/**
* Returns a map of the names of all the accounts configured in the toolkit.
*/
public Map<String, String> getAllAccountNames() {
Map<String, AccountInfo> allAccountInfo = getAllAccountInfo();
if (allAccountInfo == null) {
return Collections.<String, String>emptyMap();
}
Map<String, String> allAccountNames = new LinkedHashMap<String, String>();
for (Entry<String, AccountInfo> entry : allAccountInfo.entrySet()) {
allAccountNames.put(
entry.getKey(),
entry.getValue().getAccountName());
}
return allAccountNames;
}
/**
* Returns a map of all the accounts configured in the toolkit. This method
* returns all the legacy pref-store-based accounts if none of the profile
* accounts could be found. This method returns an empty map when it failed
* to load accounts from the credentials file and no legacy account is
* configured.
*/
public Map<String, AccountInfo> getAllAccountInfo() {
Map<String, AccountInfo> accounts = accountInfoProvider.getAllProfileAccountInfo();
// If no profile account is found, fall back to the legacy accounts
if (accounts.isEmpty()) {
accounts = accountInfoProvider.getAllLegacyPreferenceStoreAccontInfo();
}
// If even legacy accounts cannot be found, bootstrap the credentials file
if (accounts.isEmpty()) {
AwsToolkitCore.getDefault().logInfo(
String.format("No account is configued in the toolkit. " +
"Bootstrapping the credentials file at (%s).",
preferenceStore.getString(PreferenceConstants.P_CREDENTIAL_PROFILE_FILE_LOCATION)));
// boostrapCredentialsFile=true, showWarningOnFailure=false
accountInfoProvider.refreshProfileAccountInfo(true, false);
accounts = accountInfoProvider.getAllProfileAccountInfo();
}
return accounts;
}
/**
* Registers a listener to receive notifications when account info is
* changed.
*
* @param listener
* The listener to add.
*/
public void addAccountInfoChangeListener(
AccountInfoChangeListener listener) {
accountInfoProvider.addAccountInfoChangeListener(listener);
}
/**
* Stops a listener from receiving notifications when account info is
* changed.
*
* @param listener
* The listener to remove.
*/
public void removeAccountInfoChangeListener(
AccountInfoChangeListener listener) {
accountInfoProvider.removeAccountInfoChangeListener(listener);
}
/**
* Registers a listener to receive notifications when global/regional
* default accounts are changed.
*
* @param listener
* The listener to add.
*/
public void addDefaultAccountChangeListener(
PreferencePropertyChangeListener listener) {
defaultAccountMonitor.addChangeListener(listener);
}
/**
* Stops a listener from receiving notifications when global/regional
* default accounts are changed.
*
* @param listener
* The listener to remove.
*/
public void removeDefaultAccountChangeListener(
PreferencePropertyChangeListener listener) {
defaultAccountMonitor.removeChangeListener(listener);
}
private IPreferenceStore getPreferenceStore() {
return preferenceStore;
}
/**
* Returns whether there are valid aws accounts configured
*/
public boolean validAccountsConfigured() {
return getAccountInfo().isValid() || getAllAccountNames().size() > 1;
}
}