/* * Copyright 2010-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.core; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.net.URI; import java.net.URISyntaxException; import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.eclipse.core.net.proxy.IProxyChangeEvent; import org.eclipse.core.net.proxy.IProxyChangeListener; import org.eclipse.core.net.proxy.IProxyData; import org.eclipse.core.net.proxy.IProxyService; import org.eclipse.core.runtime.Status; import org.eclipse.jface.preference.IPreferenceStore; import com.amazonaws.AmazonWebServiceClient; import com.amazonaws.ClientConfiguration; import com.amazonaws.Protocol; import com.amazonaws.auth.AWSCredentials; import com.amazonaws.auth.BasicAWSCredentials; import com.amazonaws.auth.BasicSessionCredentials; import com.amazonaws.eclipse.core.accounts.AccountInfoChangeListener; import com.amazonaws.eclipse.core.preferences.PreferenceConstants; import com.amazonaws.eclipse.core.regions.Region; import com.amazonaws.eclipse.core.regions.RegionUtils; import com.amazonaws.eclipse.core.regions.Service; import com.amazonaws.eclipse.core.regions.ServiceAbbreviations; import com.amazonaws.services.autoscaling.AmazonAutoScaling; import com.amazonaws.services.autoscaling.AmazonAutoScalingClient; import com.amazonaws.services.cloudformation.AmazonCloudFormation; import com.amazonaws.services.cloudformation.AmazonCloudFormationClient; import com.amazonaws.services.cloudfront.AmazonCloudFront; import com.amazonaws.services.cloudfront.AmazonCloudFrontClient; import com.amazonaws.services.codecommit.AWSCodeCommit; import com.amazonaws.services.codecommit.AWSCodeCommitClient; import com.amazonaws.services.codedeploy.AmazonCodeDeploy; import com.amazonaws.services.codedeploy.AmazonCodeDeployClient; import com.amazonaws.services.dynamodbv2.AmazonDynamoDB; import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient; import com.amazonaws.services.ec2.AmazonEC2; import com.amazonaws.services.ec2.AmazonEC2Client; import com.amazonaws.services.elasticbeanstalk.AWSElasticBeanstalk; import com.amazonaws.services.elasticbeanstalk.AWSElasticBeanstalkClient; import com.amazonaws.services.elasticloadbalancing.AmazonElasticLoadBalancing; import com.amazonaws.services.elasticloadbalancing.AmazonElasticLoadBalancingClient; import com.amazonaws.services.identitymanagement.AmazonIdentityManagement; import com.amazonaws.services.identitymanagement.AmazonIdentityManagementClient; import com.amazonaws.services.lambda.AWSLambda; import com.amazonaws.services.lambda.AWSLambdaClient; import com.amazonaws.services.opsworks.AWSOpsWorks; import com.amazonaws.services.opsworks.AWSOpsWorksClient; import com.amazonaws.services.rds.AmazonRDS; import com.amazonaws.services.rds.AmazonRDSClient; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.AmazonS3Client; import com.amazonaws.services.securitytoken.AWSSecurityTokenService; import com.amazonaws.services.securitytoken.AWSSecurityTokenServiceClient; import com.amazonaws.services.simpledb.AmazonSimpleDB; import com.amazonaws.services.simpledb.AmazonSimpleDBClient; import com.amazonaws.services.sns.AmazonSNS; import com.amazonaws.services.sns.AmazonSNSClient; import com.amazonaws.services.sqs.AmazonSQS; import com.amazonaws.services.sqs.AmazonSQSClient; /** * Factory for creating AWS clients. */ @SuppressWarnings("deprecation") public class AWSClientFactory { /** * This constant is intended only for testing so that unit tests can * override the Eclipse preference store implementation of AccountInfo. */ public static final String ACCOUNT_INFO_OVERRIDE_PROPERTY = "com.amazonaws.eclipse.test.AccountInfoOverride"; /** Manages the cached client objects. */ private CachedClients cachedClients = new CachedClients(); /** * The identifier of the shared account info for accessing the user's * credentials. * <p> * NOTE: we used to pass an AccountInfo object into this class. That used to * work because the preference-store implementation of AccountInfo always * read the value from the external source; the only data enclosed in the * AccountInfo object is the accountId. */ private final String accountId; /** * Constructs a client factory that uses the given account identifier to * retrieve its credentials. */ public AWSClientFactory(String accountId) { this.accountId = accountId; AwsToolkitCore plugin = AwsToolkitCore.getDefault(); if ( plugin != null ) { plugin.getProxyService().addProxyChangeListener(new IProxyChangeListener() { public void proxyInfoChanged(IProxyChangeEvent event) { cachedClients.invalidateClients(); } }); plugin.getAccountManager().addAccountInfoChangeListener(new AccountInfoChangeListener() { public void onAccountInfoChange() { cachedClients.invalidateClients(); } }); } } /* * Simple getters return a client configured with the default endpoint for * the currently selected region. */ public AmazonCloudFront getCloudFrontClient() { return getCloudFrontClientByEndpoint(RegionUtils.getCurrentRegion().getServiceEndpoint(ServiceAbbreviations.CLOUDFRONT)); } /** * This is the only static API provided by this class. */ public static AmazonS3 getAnonymousS3Client() { /* * We hardcode the S3 endpoint here, since this method is used to download the * initial regions.xml file, so we can't necessarily look up an endpoint yet. * * In the future, we should ship some version of the regions.xml file, so that we * always have something available, even the first time the user runs this code. */ String serviceEndpoint = "https://s3.amazonaws.com"; ClientConfiguration clientConfiguration = createClientConfiguration(serviceEndpoint); clientConfiguration.setProtocol(Protocol.HTTP); return new AmazonS3Client((AWSCredentials)null, clientConfiguration); } public AmazonS3 getS3Client() { return getS3ClientByEndpoint(RegionUtils.getCurrentRegion().getServiceEndpoint(ServiceAbbreviations.S3)); } /** * Returns a client for the region where the given bucket resides. No * caching is performed on the region lookup. */ public AmazonS3 getS3ClientForBucket(String bucketName) { String serviceEndpoint = getS3BucketEndpoint(bucketName); return getS3ClientByEndpoint(serviceEndpoint); } /** * Returns the endpoint appropriate to the given bucket. */ public String getS3BucketEndpoint(String bucketName) { AmazonS3 globalS3Client = getS3Client(); String bucketLocation = globalS3Client.getBucketLocation(bucketName); String region = bucketLocation; if ( bucketLocation == null || bucketLocation.equals("US") ) { region = "us-east-1"; } String serviceEndpoint = RegionUtils.getRegion(region).getServiceEndpoint(ServiceAbbreviations.S3); return serviceEndpoint; } public AmazonSimpleDB getSimpleDBClient() { return getSimpleDBClientByEndpoint(RegionUtils.getCurrentRegion().getServiceEndpoint(ServiceAbbreviations.SIMPLEDB)); } public AmazonRDS getRDSClient() { return getRDSClientByEndpoint(RegionUtils.getCurrentRegion().getServiceEndpoint(ServiceAbbreviations.RDS)); } public AmazonSQS getSQSClient() { return getSQSClientByEndpoint(RegionUtils.getCurrentRegion().getServiceEndpoint(ServiceAbbreviations.SQS)); } public AmazonSNS getSNSClient() { return getSNSClientByEndpoint(RegionUtils.getCurrentRegion().getServiceEndpoint(ServiceAbbreviations.SNS)); } public AmazonDynamoDB getDynamoDBV2Client() { return getDynamoDBV2ClientByEndpoint(RegionUtils.getCurrentRegion().getServiceEndpoint(ServiceAbbreviations.DYNAMODB)); } public AWSSecurityTokenService getSecurityTokenServiceClient() { return getSecurityTokenServiceByEndpoint(RegionUtils.getCurrentRegion().getServiceEndpoint(ServiceAbbreviations.STS)); } public AWSElasticBeanstalk getElasticBeanstalkClient() { return getElasticBeanstalkClientByEndpoint(RegionUtils.getCurrentRegion().getServiceEndpoint(ServiceAbbreviations.BEANSTALK)); } public AmazonIdentityManagement getIAMClient() { return getIAMClientByEndpoint(RegionUtils.getCurrentRegion().getServiceEndpoint(ServiceAbbreviations.IAM)); } public AmazonCloudFormation getCloudFormationClient() { return getCloudFormationClientByEndpoint(RegionUtils.getCurrentRegion().getServiceEndpoint(ServiceAbbreviations.CLOUD_FORMATION)); } public AmazonEC2 getEC2Client() { return getEC2ClientByEndpoint(RegionUtils.getCurrentRegion().getServiceEndpoint(ServiceAbbreviations.EC2)); } public AmazonCodeDeploy getCodeDeployClient() { return getCodeDeployClientByEndpoint(RegionUtils.getCurrentRegion().getServiceEndpoint(ServiceAbbreviations.CODE_DEPLOY)); } public AWSOpsWorks getOpsWorksClient() { return getOpsWorksClientByEndpoint(RegionUtils.getCurrentRegion().getServiceEndpoint(ServiceAbbreviations.OPSWORKS)); } public AWSLambda getLambdaClient() { return getLambdaClientByEndpoint(RegionUtils.getCurrentRegion().getServiceEndpoint(ServiceAbbreviations.LAMBDA)); } public AWSCodeCommit getCodeCommitClient() { return getCodeCommitClientByEndpoint(RegionUtils.getCurrentRegion().getServiceEndpoint(ServiceAbbreviations.CODECOMMIT)); } /* * Endpoint-specific getters return clients that use the endpoint given. */ public AmazonIdentityManagement getIAMClientByEndpoint(String endpoint) { return getOrCreateClient(endpoint, AmazonIdentityManagementClient.class); } public AmazonCloudFront getCloudFrontClientByEndpoint(String endpoint) { return getOrCreateClient(endpoint, AmazonCloudFrontClient.class); } public AmazonS3 getS3ClientByEndpoint(String endpoint) { return getOrCreateClient(endpoint, AmazonS3Client.class); } public AmazonEC2 getEC2ClientByEndpoint(String endpoint) { return getOrCreateClient(endpoint, AmazonEC2Client.class); } public AmazonSimpleDB getSimpleDBClientByEndpoint(final String endpoint) { return getOrCreateClient(endpoint, AmazonSimpleDBClient.class); } public AmazonRDS getRDSClientByEndpoint(final String endpoint) { return getOrCreateClient(endpoint, AmazonRDSClient.class); } public AmazonSQS getSQSClientByEndpoint(final String endpoint) { return getOrCreateClient(endpoint, AmazonSQSClient.class); } public AmazonSNS getSNSClientByEndpoint(final String endpoint) { return getOrCreateClient(endpoint, AmazonSNSClient.class); } public AWSElasticBeanstalk getElasticBeanstalkClientByEndpoint(String endpoint) { return getOrCreateClient(endpoint, AWSElasticBeanstalkClient.class); } public AmazonElasticLoadBalancing getElasticLoadBalancingClientByEndpoint(String endpoint) { return getOrCreateClient(endpoint, AmazonElasticLoadBalancingClient.class); } public AmazonAutoScaling getAutoScalingClientByEndpoint(String endpoint) { return getOrCreateClient(endpoint, AmazonAutoScalingClient.class); } public AmazonDynamoDB getDynamoDBClientByEndpoint(String endpoint) { return getOrCreateClient(endpoint, AmazonDynamoDBClient.class); } public com.amazonaws.services.dynamodbv2.AmazonDynamoDB getDynamoDBV2ClientByEndpoint(String endpoint) { return getOrCreateClient(endpoint, com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient.class); } public AWSSecurityTokenService getSecurityTokenServiceByEndpoint(String endpoint) { return getOrCreateClient(endpoint, AWSSecurityTokenServiceClient.class); } public AmazonCloudFormation getCloudFormationClientByEndpoint(String endpoint) { return getOrCreateClient(endpoint, AmazonCloudFormationClient.class); } public AmazonCodeDeploy getCodeDeployClientByEndpoint(String endpoint) { return getOrCreateClient(endpoint, AmazonCodeDeployClient.class); } public AWSOpsWorks getOpsWorksClientByEndpoint(String endpoint) { return getOrCreateClient(endpoint, AWSOpsWorksClient.class); } public AWSLambda getLambdaClientByEndpoint(String endpoint) { return getOrCreateClient(endpoint, AWSLambdaClient.class); } public AWSCodeCommit getCodeCommitClientByEndpoint(String endpoint) { return getOrCreateClient(endpoint, AWSCodeCommitClient.class); } private <T extends AmazonWebServiceClient> T getOrCreateClient(String endpoint, Class<T> clientClass) { synchronized (clientClass) { if ( cachedClients.getClient(endpoint, clientClass) == null ) { cachedClients.cacheClient(endpoint, createClient(endpoint, clientClass)); } } return cachedClients.getClient(endpoint, clientClass); } private <T extends AmazonWebServiceClient> T createClient(String endpoint, Class<T> clientClass) { try { Constructor<T> constructor = clientClass.getConstructor(AWSCredentials.class, ClientConfiguration.class); ClientConfiguration config = createClientConfiguration(endpoint); Service service = RegionUtils.getServiceByEndpoint(endpoint); config.setSignerOverride(service.getSignerOverride()); final AccountInfo accountInfo = AwsToolkitCore.getDefault() .getAccountManager().getAccountInfo(accountId); AWSCredentials credentials = null; if (accountInfo.isUseSessionToken()) { credentials = new BasicSessionCredentials( accountInfo.getAccessKey(), accountInfo.getSecretKey(), accountInfo.getSessionToken()); } else { credentials = new BasicAWSCredentials( accountInfo.getAccessKey(), accountInfo.getSecretKey()); } T client = constructor.newInstance(credentials, config); /* * If a serviceId is explicitly specified with the region metadata, * and this client has a 3-argument form of setEndpoint (for sigv4 * overrides), then explicitly pass in the service Id and region Id * in case it can't be parsed from the endpoint URL by the default * setEndpoint method. */ Method sigv4SetEndpointMethod = lookupSigV4SetEndpointMethod(clientClass); if (service.getServiceId() != null && sigv4SetEndpointMethod != null) { Region region = RegionUtils.getRegionByEndpoint(endpoint); sigv4SetEndpointMethod.invoke(client, endpoint, service.getServiceId(), region.getId()); } else { client.setEndpoint(endpoint); } return client; } catch (Exception e) { throw new RuntimeException("Unable to create client: " + e.getMessage(), e); } } /** * Returns the 3-argument form of setEndpoint that is used to override * values for sigv4 signing, or null if the specified class does not support * that version of setEndpoint. * * @param clientClass * The class to introspect. * * @return The 3-argument method form of setEndpoint, or null if it doesn't * exist in the specified class. */ private <T extends AmazonWebServiceClient> Method lookupSigV4SetEndpointMethod(Class<T> clientClass) { try { return clientClass.getMethod("setEndpoint", String.class, String.class, String.class); } catch (SecurityException e) { throw new RuntimeException("Unable to lookup class methods via reflection", e); } catch (NoSuchMethodException e) { return null; } } private static ClientConfiguration createClientConfiguration(String secureEndpoint) { ClientConfiguration config = new ClientConfiguration(); IPreferenceStore preferences = AwsToolkitCore.getDefault().getPreferenceStore(); int connectionTimeout = preferences.getInt(PreferenceConstants.P_CONNECTION_TIMEOUT); int socketTimeout = preferences.getInt(PreferenceConstants.P_SOCKET_TIMEOUT); config.setConnectionTimeout(connectionTimeout); config.setSocketTimeout(socketTimeout); config.setUserAgent(AwsClientUtils.formatUserAgentString("AWS-Toolkit-For-Eclipse", AwsToolkitCore.getDefault())); AwsToolkitCore plugin = AwsToolkitCore.getDefault(); if ( plugin != null ) { IProxyService proxyService = AwsToolkitCore.getDefault().getProxyService(); if ( proxyService.isProxiesEnabled() ) { try { IProxyData[] proxyData; proxyData = proxyService.select(new URI(secureEndpoint)); if ( proxyData.length > 0 ) { config.setProxyHost(proxyData[0].getHost()); config.setProxyPort(proxyData[0].getPort()); if ( proxyData[0].isRequiresAuthentication() ) { config.setProxyUsername(proxyData[0].getUserId()); config.setProxyPassword(proxyData[0].getPassword()); } } } catch ( URISyntaxException e ) { plugin.getLog().log(new Status(Status.ERROR, AwsToolkitCore.PLUGIN_ID, e.getMessage(), e)); } } } return config; } /** * Responsible for managing the various AWS client objects needed for each service/region combination. */ private static class CachedClients { private final Map<Class<?>, Map<String, Object>> cachedClientsByEndpoint = Collections .synchronizedMap(new HashMap<Class<?>, Map<String, Object>>()); @SuppressWarnings("unchecked") public <T> T getClient(String endpoint, Class<T> clientClass) { if (cachedClientsByEndpoint.get(clientClass) == null) { return null; } return (T)cachedClientsByEndpoint.get(clientClass).get(endpoint); } public <T> void cacheClient(String endpoint, T client) { if (cachedClientsByEndpoint.get(client.getClass()) == null) { cachedClientsByEndpoint.put(client.getClass(), new HashMap<String, Object>()); } Map<String, Object> map = cachedClientsByEndpoint.get(client.getClass()); map.put(endpoint, client); } public synchronized void invalidateClients() { cachedClientsByEndpoint.clear(); } } }