All options above can be configured by users, and the overridden value will take precedence.")
+ .add("
Note: for any mode other than {@link #LEGACY}, the vended default values might change "
+ + "as best practices may evolve. As a result, it is encouraged to perform testing when upgrading the SDK if"
+ + " you are using a mode other than {@link #LEGACY}")
+ .add(System.lineSeparator());
+
+ return builder.add("
While the {@link #LEGACY} defaults mode is specific to Java, other modes are "
+ + "standardized across "
+ + "all of the AWS SDKs
")
+ .add(System.lineSeparator())
+ .add("
The defaults mode can be configured:")
+ .add(System.lineSeparator())
+ .add("
")
+ .add("
Directly on a client via {@code AwsClientBuilder.Builder#defaultsMode"
+ + "(DefaultsMode)}.
")
+ .add(System.lineSeparator())
+ .add("
On a configuration profile via the \"defaults_mode\" profile file property.
")
+ .add(System.lineSeparator())
+ .add("
Globally via the \"aws.defaultsMode\" system property.
")
+ .add("
Globally via the \"AWS_DEFAULTS_MODE\" environment variable.
")
+ .add("")
+ .build();
+ }
+
+
+ private MethodSpec fromValueSpec() {
+ return MethodSpec.methodBuilder("fromValue")
+ .returns(className())
+ .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
+ .addJavadoc("Use this in place of valueOf to convert the raw string returned by the service into the " +
+ "enum value.\n\n" +
+ "@param $N real value\n" +
+ "@return $T corresponding to the value\n", VALUE, className())
+ .addParameter(String.class, VALUE)
+ .addStatement("$T.paramNotNull(value, $S)", Validate.class, VALUE)
+ .beginControlFlow("if (!VALUE_MAP.containsKey(value))")
+ .addStatement("throw new IllegalArgumentException($S + value)", "The provided value is not a"
+ + " valid "
+ + "defaults mode ")
+ .endControlFlow()
+ .addStatement("return $N.get($N)", VALUE_MAP, VALUE)
+ .build();
+ }
+
+ private MethodSpec createConstructor() {
+ return MethodSpec.constructorBuilder()
+ .addModifiers(Modifier.PRIVATE)
+ .addParameter(String.class, VALUE)
+ .addStatement("this.$1N = $1N", VALUE)
+ .build();
+ }
+
+ private static MethodSpec.Builder toStringBuilder() {
+ return MethodSpec.methodBuilder("toString")
+ .returns(String.class)
+ .addModifiers(Modifier.PUBLIC)
+ .addAnnotation(Override.class);
+ }
+
+}
diff --git a/codegen-lite/src/test/java/software/amazon/awssdk/codegen/lite/defaultsmode/DefaultsModeGenerationTest.java b/codegen-lite/src/test/java/software/amazon/awssdk/codegen/lite/defaultsmode/DefaultsModeGenerationTest.java
new file mode 100644
index 000000000000..9ceb3c9e40d4
--- /dev/null
+++ b/codegen-lite/src/test/java/software/amazon/awssdk/codegen/lite/defaultsmode/DefaultsModeGenerationTest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 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 software.amazon.awssdk.codegen.lite.defaultsmode;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static software.amazon.awssdk.codegen.lite.PoetMatchers.generatesTo;
+
+import java.io.File;
+import java.nio.file.Paths;
+import org.junit.Before;
+import org.junit.Test;
+
+public class DefaultsModeGenerationTest {
+
+ private static final String DEFAULT_CONFIGURATION = "/software/amazon/awssdk/codegen/lite/test-sdk-default-configuration.json";
+ private static final String DEFAULTS_MODE_BASE = "software.amazon.awssdk.defaultsmode";
+
+ private File file;
+ private DefaultConfiguration defaultConfiguration;
+
+ @Before
+ public void before() throws Exception {
+ this.file = Paths.get(getClass().getResource(DEFAULT_CONFIGURATION).toURI()).toFile();
+ this.defaultConfiguration = DefaultsLoader.load(file);
+ }
+
+ @Test
+ public void defaultsModeEnum() {
+ DefaultsModeGenerator generator = new DefaultsModeGenerator(DEFAULTS_MODE_BASE, defaultConfiguration);
+ assertThat(generator, generatesTo("defaults-mode.java"));
+ }
+
+ @Test
+ public void defaultsModeConfigurationClass() {
+ DefaultsModeConfigurationGenerator generator = new DefaultsModeConfigurationGenerator(DEFAULTS_MODE_BASE, DEFAULTS_MODE_BASE, defaultConfiguration);
+ assertThat(generator, generatesTo("defaults-mode-configuration.java"));
+ }
+
+}
diff --git a/codegen-lite/src/test/resources/software/amazon/awssdk/codegen/lite/defaultsmode/defaults-mode-configuration.java b/codegen-lite/src/test/resources/software/amazon/awssdk/codegen/lite/defaultsmode/defaults-mode-configuration.java
new file mode 100644
index 000000000000..d1e73a79daa7
--- /dev/null
+++ b/codegen-lite/src/test/resources/software/amazon/awssdk/codegen/lite/defaultsmode/defaults-mode-configuration.java
@@ -0,0 +1,89 @@
+package software.amazon.awssdk.defaultsmode;
+
+import java.time.Duration;
+import java.util.EnumMap;
+import java.util.Map;
+import software.amazon.awssdk.annotations.Generated;
+import software.amazon.awssdk.annotations.SdkInternalApi;
+import software.amazon.awssdk.core.client.config.SdkClientOption;
+import software.amazon.awssdk.core.retry.RetryMode;
+import software.amazon.awssdk.http.SdkHttpConfigurationOption;
+import software.amazon.awssdk.regions.ServiceMetadataAdvancedOption;
+import software.amazon.awssdk.utils.AttributeMap;
+
+/**
+ * Contains a collection of default configuration options for each DefaultsMode
+ */
+@SdkInternalApi
+@Generated("software.amazon.awssdk:codegen")
+public final class DefaultsModeConfiguration {
+ private static final AttributeMap STANDARD_DEFAULTS = AttributeMap.builder()
+ .put(SdkClientOption.DEFAULT_RETRY_MODE, RetryMode.STANDARD)
+ .put(ServiceMetadataAdvancedOption.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT, "regional").build();
+
+ private static final AttributeMap STANDARD_HTTP_DEFAULTS = AttributeMap.builder()
+ .put(SdkHttpConfigurationOption.CONNECTION_TIMEOUT, Duration.ofMillis(2000))
+ .put(SdkHttpConfigurationOption.TLS_NEGOTIATION_TIMEOUT, Duration.ofMillis(2000)).build();
+
+ private static final AttributeMap MOBILE_DEFAULTS = AttributeMap.builder()
+ .put(SdkClientOption.DEFAULT_RETRY_MODE, RetryMode.ADAPTIVE)
+ .put(ServiceMetadataAdvancedOption.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT, "regional").build();
+
+ private static final AttributeMap MOBILE_HTTP_DEFAULTS = AttributeMap.builder()
+ .put(SdkHttpConfigurationOption.CONNECTION_TIMEOUT, Duration.ofMillis(10000))
+ .put(SdkHttpConfigurationOption.TLS_NEGOTIATION_TIMEOUT, Duration.ofMillis(11000)).build();
+
+ private static final AttributeMap CROSS_REGION_DEFAULTS = AttributeMap.builder()
+ .put(SdkClientOption.DEFAULT_RETRY_MODE, RetryMode.STANDARD)
+ .put(ServiceMetadataAdvancedOption.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT, "regional").build();
+
+ private static final AttributeMap CROSS_REGION_HTTP_DEFAULTS = AttributeMap.builder()
+ .put(SdkHttpConfigurationOption.CONNECTION_TIMEOUT, Duration.ofMillis(2800))
+ .put(SdkHttpConfigurationOption.TLS_NEGOTIATION_TIMEOUT, Duration.ofMillis(2800)).build();
+
+ private static final AttributeMap IN_REGION_DEFAULTS = AttributeMap.builder()
+ .put(SdkClientOption.DEFAULT_RETRY_MODE, RetryMode.STANDARD)
+ .put(ServiceMetadataAdvancedOption.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT, "regional").build();
+
+ private static final AttributeMap IN_REGION_HTTP_DEFAULTS = AttributeMap.builder()
+ .put(SdkHttpConfigurationOption.CONNECTION_TIMEOUT, Duration.ofMillis(1000))
+ .put(SdkHttpConfigurationOption.TLS_NEGOTIATION_TIMEOUT, Duration.ofMillis(1000)).build();
+
+ private static final AttributeMap LEGACY_DEFAULTS = AttributeMap.empty();
+
+ private static final AttributeMap LEGACY_HTTP_DEFAULTS = AttributeMap.empty();
+
+ private static final Map DEFAULT_CONFIG_BY_MODE = new EnumMap<>(DefaultsMode.class);
+
+ private static final Map DEFAULT_HTTP_CONFIG_BY_MODE = new EnumMap<>(DefaultsMode.class);
+
+ static {
+ DEFAULT_CONFIG_BY_MODE.put(DefaultsMode.STANDARD, STANDARD_DEFAULTS);
+ DEFAULT_CONFIG_BY_MODE.put(DefaultsMode.MOBILE, MOBILE_DEFAULTS);
+ DEFAULT_CONFIG_BY_MODE.put(DefaultsMode.CROSS_REGION, CROSS_REGION_DEFAULTS);
+ DEFAULT_CONFIG_BY_MODE.put(DefaultsMode.IN_REGION, IN_REGION_DEFAULTS);
+ DEFAULT_CONFIG_BY_MODE.put(DefaultsMode.LEGACY, LEGACY_DEFAULTS);
+ DEFAULT_HTTP_CONFIG_BY_MODE.put(DefaultsMode.STANDARD, STANDARD_HTTP_DEFAULTS);
+ DEFAULT_HTTP_CONFIG_BY_MODE.put(DefaultsMode.MOBILE, MOBILE_HTTP_DEFAULTS);
+ DEFAULT_HTTP_CONFIG_BY_MODE.put(DefaultsMode.CROSS_REGION, CROSS_REGION_HTTP_DEFAULTS);
+ DEFAULT_HTTP_CONFIG_BY_MODE.put(DefaultsMode.IN_REGION, IN_REGION_HTTP_DEFAULTS);
+ DEFAULT_HTTP_CONFIG_BY_MODE.put(DefaultsMode.LEGACY, LEGACY_HTTP_DEFAULTS);
+ }
+
+ private DefaultsModeConfiguration() {
+ }
+
+ /**
+ * Return the default config options for a given defaults mode
+ */
+ public static AttributeMap defaultConfig(DefaultsMode mode) {
+ return DEFAULT_CONFIG_BY_MODE.getOrDefault(mode, AttributeMap.empty());
+ }
+
+ /**
+ * Return the default config options for a given defaults mode
+ */
+ public static AttributeMap defaultHttpConfig(DefaultsMode mode) {
+ return DEFAULT_HTTP_CONFIG_BY_MODE.getOrDefault(mode, AttributeMap.empty());
+ }
+}
diff --git a/codegen-lite/src/test/resources/software/amazon/awssdk/codegen/lite/defaultsmode/defaults-mode.java b/codegen-lite/src/test/resources/software/amazon/awssdk/codegen/lite/defaultsmode/defaults-mode.java
new file mode 100644
index 000000000000..01c77d2f12d4
--- /dev/null
+++ b/codegen-lite/src/test/resources/software/amazon/awssdk/codegen/lite/defaultsmode/defaults-mode.java
@@ -0,0 +1,96 @@
+package software.amazon.awssdk.defaultsmode;
+
+import java.util.Map;
+import software.amazon.awssdk.annotations.Generated;
+import software.amazon.awssdk.annotations.SdkPublicApi;
+import software.amazon.awssdk.utils.Validate;
+import software.amazon.awssdk.utils.internal.EnumUtils;
+
+/**
+ * A defaults mode determines how certain default configuration options are resolved in the SDK. Based on the provided
+ * mode, the SDK will vend sensible default values tailored to the mode for the following settings:
+ *
+ *
retryMode: PLACEHOLDER
+ *
s3UsEast1RegionalEndpoints: PLACEHOLDER
+ *
connectTimeoutInMillis: PLACEHOLDER
+ *
tlsNegotiationTimeoutInMillis: PLACEHOLDER
+ *
+ *
+ * All options above can be configured by users, and the overridden value will take precedence.
+ *
+ * Note: for any mode other than {@link #LEGACY}, the vended default values might change as best practices may
+ * evolve. As a result, it is encouraged to perform testing when upgrading the SDK if you are using a mode other than
+ * {@link #LEGACY}
+ *
+ * While the {@link #LEGACY} defaults mode is specific to Java, other modes are standardized across all of the AWS SDKs
+ *
+ *
+ * The defaults mode can be configured:
+ *
+ *
Directly on a client via {@code AwsClientBuilder.Builder#defaultsMode(DefaultsMode)}.
+ *
On a configuration profile via the "defaults_mode" profile file property.
+ *
Globally via the "aws.defaultsMode" system property.
+ *
Globally via the "AWS_DEFAULTS_MODE" environment variable.
+ *
+ */
+@SdkPublicApi
+@Generated("software.amazon.awssdk:codegen")
+public enum DefaultsMode {
+ /**
+ * PLACEHOLDER
+ */
+ LEGACY("legacy"),
+
+ /**
+ * PLACEHOLDER
+ */
+ STANDARD("standard"),
+
+ /**
+ * PLACEHOLDER
+ */
+ MOBILE("mobile"),
+
+ /**
+ * PLACEHOLDER
+ */
+ CROSS_REGION("cross-region"),
+
+ /**
+ * PLACEHOLDER
+ */
+ IN_REGION("in-region"),
+
+ /**
+ * PLACEHOLDER
+ */
+ AUTO("auto");
+
+ private static final Map VALUE_MAP = EnumUtils.uniqueIndex(DefaultsMode.class, DefaultsMode::toString);
+
+ private final String value;
+
+ private DefaultsMode(String value) {
+ this.value = value;
+ }
+
+ /**
+ * Use this in place of valueOf to convert the raw string returned by the service into the enum value.
+ *
+ * @param value
+ * real value
+ * @return DefaultsMode corresponding to the value
+ */
+ public static DefaultsMode fromValue(String value) {
+ Validate.paramNotNull(value, "value");
+ if (!VALUE_MAP.containsKey(value)) {
+ throw new IllegalArgumentException("The provided value is not a valid defaults mode " + value);
+ }
+ return VALUE_MAP.get(value);
+ }
+
+ @Override
+ public String toString() {
+ return String.valueOf(value);
+ }
+}
diff --git a/codegen-lite/src/test/resources/software/amazon/awssdk/codegen/lite/test-sdk-default-configuration.json b/codegen-lite/src/test/resources/software/amazon/awssdk/codegen/lite/test-sdk-default-configuration.json
new file mode 100644
index 000000000000..5c9f0fab48b7
--- /dev/null
+++ b/codegen-lite/src/test/resources/software/amazon/awssdk/codegen/lite/test-sdk-default-configuration.json
@@ -0,0 +1,64 @@
+{
+ "version": 1,
+ "base": {
+ "retryMode": "standard",
+ "stsRegionalEndpoints": "regional",
+ "s3UsEast1RegionalEndpoints": "regional",
+ "connectTimeoutInMillis": 1000,
+ "tlsNegotiationTimeoutInMillis": 1000
+ },
+ "modes": {
+ "standard": {
+ "connectTimeoutInMillis": {
+ "multiply":2
+ },
+ "tlsNegotiationTimeoutInMillis": {
+ "multiply":2
+ }
+ },
+ "in-region": {
+ "connectTimeoutInMillis": {
+ "multiply": 1
+ },
+ "tlsNegotiationTimeoutInMillis": {
+ "multiply": 1
+ }
+ },
+ "cross-region": {
+ "connectTimeoutInMillis": {
+ "multiply": 2.8
+ },
+ "tlsNegotiationTimeoutInMillis": {
+ "multiply": 2.8
+ }
+ },
+ "mobile": {
+ "connectTimeoutInMillis": {
+ "override": 10000
+ },
+ "tlsNegotiationTimeoutInMillis": {
+ "add": 10000
+ },
+ "retryMode": {
+ "override": "adaptive"
+ }
+ }
+ },
+ "documentation": {
+ "modes": {
+ "standard": "PLACEHOLDER",
+ "in-region": "PLACEHOLDER",
+ "cross-region": "PLACEHOLDER",
+ "mobile": "PLACEHOLDER",
+ "auto": "PLACEHOLDER",
+ "legacy": "PLACEHOLDER"
+ },
+ "configuration": {
+ "retryMode": "PLACEHOLDER",
+ "stsRegionalEndpoints": "PLACEHOLDER",
+ "s3UsEast1RegionalEndpoints": "PLACEHOLDER",
+ "connectTimeoutInMillis": "PLACEHOLDER",
+ "tlsNegotiationTimeoutInMillis": "PLACEHOLDER"
+ }
+ }
+}
\ No newline at end of file
diff --git a/core/aws-core/pom.xml b/core/aws-core/pom.xml
index 7561698efd00..f597d3e1cb29 100644
--- a/core/aws-core/pom.xml
+++ b/core/aws-core/pom.xml
@@ -169,6 +169,19 @@
+
+ software.amazon.awssdk
+ codegen-lite-maven-plugin
+ ${awsjavasdk.version}
+
+
+ generate-sources
+
+ generate-defaults-mode
+
+
+
+
diff --git a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/builder/AwsClientBuilder.java b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/builder/AwsClientBuilder.java
index 08c1175b5dd9..27744a58a572 100644
--- a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/builder/AwsClientBuilder.java
+++ b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/builder/AwsClientBuilder.java
@@ -17,6 +17,7 @@
import software.amazon.awssdk.annotations.SdkPublicApi;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
+import software.amazon.awssdk.awscore.defaultsmode.DefaultsMode;
import software.amazon.awssdk.core.client.builder.SdkClientBuilder;
import software.amazon.awssdk.regions.Region;
@@ -66,6 +67,26 @@ public interface AwsClientBuilder
+ * If this is not specified, the SDK will attempt to identify the defaults mode automatically using the following logic:
+ *
+ *
Check the "defaults_mode" profile file property.
+ *
Check "aws.defaultsMode" system property.
+ *
Check the "AWS_DEFAULTS_MODE" environment variable.
+ *
+ *
+ * @param defaultsMode the defaultsMode to use
+ * @return This object for method chaining.
+ * @see DefaultsMode
+ */
+ default BuilderT defaultsMode(DefaultsMode defaultsMode) {
+ throw new UnsupportedOperationException();
+ }
+
/**
* Configure whether the SDK should use the AWS dualstack endpoint.
*
diff --git a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/builder/AwsDefaultClientBuilder.java b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/builder/AwsDefaultClientBuilder.java
index 2857aab0c2a9..b522b5f8d678 100644
--- a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/builder/AwsDefaultClientBuilder.java
+++ b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/builder/AwsDefaultClientBuilder.java
@@ -15,6 +15,8 @@
package software.amazon.awssdk.awscore.client.builder;
+import static software.amazon.awssdk.awscore.client.config.AwsClientOption.DEFAULTS_MODE;
+
import java.net.URI;
import java.util.Arrays;
import java.util.List;
@@ -25,11 +27,15 @@
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
import software.amazon.awssdk.awscore.client.config.AwsAdvancedClientOption;
import software.amazon.awssdk.awscore.client.config.AwsClientOption;
+import software.amazon.awssdk.awscore.defaultsmode.DefaultsMode;
import software.amazon.awssdk.awscore.endpoint.DefaultServiceEndpointBuilder;
import software.amazon.awssdk.awscore.endpoint.DualstackEnabledProvider;
import software.amazon.awssdk.awscore.endpoint.FipsEnabledProvider;
import software.amazon.awssdk.awscore.eventstream.EventStreamInitialRequestInterceptor;
import software.amazon.awssdk.awscore.interceptor.HelpfulUnknownHostExceptionInterceptor;
+import software.amazon.awssdk.awscore.internal.defaultsmode.AutoDefaultsModeDiscovery;
+import software.amazon.awssdk.awscore.internal.defaultsmode.DefaultsModeConfiguration;
+import software.amazon.awssdk.awscore.internal.defaultsmode.DefaultsModeResolver;
import software.amazon.awssdk.awscore.retry.AwsRetryPolicy;
import software.amazon.awssdk.core.client.builder.SdkDefaultClientBuilder;
import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption;
@@ -43,9 +49,11 @@
import software.amazon.awssdk.profiles.ProfileFile;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.regions.ServiceMetadata;
+import software.amazon.awssdk.regions.ServiceMetadataAdvancedOption;
import software.amazon.awssdk.regions.providers.DefaultAwsRegionProviderChain;
import software.amazon.awssdk.utils.AttributeMap;
import software.amazon.awssdk.utils.CollectionUtils;
+import software.amazon.awssdk.utils.Logger;
/**
* An SDK-internal implementation of the methods in {@link AwsClientBuilder}, {@link AwsAsyncClientBuilder} and
@@ -69,16 +77,21 @@
public abstract class AwsDefaultClientBuilder, ClientT>
extends SdkDefaultClientBuilder
implements AwsClientBuilder {
+ private static final Logger log = Logger.loggerFor(AwsClientBuilder.class);
private static final String DEFAULT_ENDPOINT_PROTOCOL = "https";
+ private final AutoDefaultsModeDiscovery autoDefaultsModeDiscovery;
protected AwsDefaultClientBuilder() {
super();
+ autoDefaultsModeDiscovery = new AutoDefaultsModeDiscovery();
}
@SdkTestInternalApi
AwsDefaultClientBuilder(SdkHttpClient.Builder defaultHttpClientBuilder,
- SdkAsyncHttpClient.Builder defaultAsyncHttpClientFactory) {
+ SdkAsyncHttpClient.Builder defaultAsyncHttpClientFactory,
+ AutoDefaultsModeDiscovery autoDefaultsModeDiscovery) {
super(defaultHttpClientBuilder, defaultAsyncHttpClientFactory);
+ this.autoDefaultsModeDiscovery = autoDefaultsModeDiscovery;
}
/**
@@ -105,6 +118,19 @@ protected final AttributeMap childHttpConfig() {
return serviceHttpConfig();
}
+ /**
+ * Return HTTP related defaults with the following chain of priorities.
+ *
+ *
+ *
+ */
@Override
protected final SdkClientConfiguration finalizeChildConfiguration(SdkClientConfiguration configuration) {
configuration = finalizeServiceConfiguration(configuration);
@@ -148,6 +181,8 @@ protected final SdkClientConfiguration finalizeChildConfiguration(SdkClientConfi
.option(AwsClientOption.FIPS_ENDPOINT_ENABLED, resolveFipsEndpointEnabled(configuration))
.build();
+ configuration = mergeSmartDefaults(configuration);
+
return configuration.toBuilder()
.option(AwsClientOption.CREDENTIALS_PROVIDER, resolveCredentials(configuration))
.option(SdkClientOption.ENDPOINT, resolveEndpoint(configuration))
@@ -157,6 +192,19 @@ protected final SdkClientConfiguration finalizeChildConfiguration(SdkClientConfi
.build();
}
+ private SdkClientConfiguration mergeSmartDefaults(SdkClientConfiguration configuration) {
+ DefaultsMode defaultsMode = resolveDefaultsMode(configuration);
+ AttributeMap defaultConfig = DefaultsModeConfiguration.defaultConfig(defaultsMode);
+ return configuration.toBuilder()
+ .option(DEFAULTS_MODE, defaultsMode)
+ .build()
+ .merge(c -> c.option(SdkClientOption.DEFAULT_RETRY_MODE,
+ defaultConfig.get(SdkClientOption.DEFAULT_RETRY_MODE))
+ .option(ServiceMetadataAdvancedOption.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT,
+ defaultConfig.get(
+ ServiceMetadataAdvancedOption.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT)));
+ }
+
/**
* Optionally overridden by child classes to derive service-specific configuration from the default-applied configuration.
*/
@@ -164,6 +212,14 @@ protected SdkClientConfiguration finalizeServiceConfiguration(SdkClientConfigura
return configuration;
}
+ /**
+ * Merged the HTTP defaults specified for each {@link DefaultsMode}
+ */
+ private AttributeMap mergeSmartHttpDefaults(SdkClientConfiguration configuration, AttributeMap attributeMap) {
+ DefaultsMode defaultsMode = configuration.option(DEFAULTS_MODE);
+ return attributeMap.merge(DefaultsModeConfiguration.defaultHttpConfig(defaultsMode));
+ }
+
/**
* Resolve the signing region from the default-applied configuration.
*/
@@ -185,6 +241,8 @@ private URI endpointFromConfig(SdkClientConfiguration config) {
.withRegion(config.option(AwsClientOption.AWS_REGION))
.withProfileFile(() -> config.option(SdkClientOption.PROFILE_FILE))
.withProfileName(config.option(SdkClientOption.PROFILE_NAME))
+ .putAdvancedOption(ServiceMetadataAdvancedOption.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT,
+ config.option(ServiceMetadataAdvancedOption.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT))
.withDualstackEnabled(config.option(AwsClientOption.DUALSTACK_ENDPOINT_ENABLED))
.withFipsEnabled(config.option(AwsClientOption.FIPS_ENDPOINT_ENABLED))
.getServiceEndpoint();
@@ -217,6 +275,25 @@ private Region regionFromDefaultProvider(SdkClientConfiguration config) {
.getRegion();
}
+ private DefaultsMode resolveDefaultsMode(SdkClientConfiguration config) {
+ DefaultsMode defaultsMode =
+ config.option(AwsClientOption.DEFAULTS_MODE) != null ?
+ config.option(AwsClientOption.DEFAULTS_MODE) :
+ DefaultsModeResolver.create()
+ .profileFile(() -> config.option(SdkClientOption.PROFILE_FILE))
+ .profileName(config.option(SdkClientOption.PROFILE_NAME))
+ .resolve();
+
+ if (defaultsMode == DefaultsMode.AUTO) {
+ defaultsMode = autoDefaultsModeDiscovery.discover(config.option(AwsClientOption.AWS_REGION));
+ DefaultsMode finalDefaultsMode = defaultsMode;
+ log.debug(() -> String.format("Resolved %s client's AUTO configuration mode to %s", serviceName(),
+ finalDefaultsMode));
+ }
+
+ return defaultsMode;
+ }
+
/**
* Resolve whether a dualstack endpoint should be used for this client.
*/
@@ -344,4 +421,14 @@ private List awsInterceptors() {
return Arrays.asList(new HelpfulUnknownHostExceptionInterceptor(),
new EventStreamInitialRequestInterceptor());
}
+
+ @Override
+ public final BuilderT defaultsMode(DefaultsMode defaultsMode) {
+ clientConfiguration.option(DEFAULTS_MODE, defaultsMode);
+ return thisBuilder();
+ }
+
+ public final void setDefaultsMode(DefaultsMode defaultsMode) {
+ defaultsMode(defaultsMode);
+ }
}
diff --git a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/config/AwsClientOption.java b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/config/AwsClientOption.java
index a8c6cdb22b70..2d1b665916c2 100644
--- a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/config/AwsClientOption.java
+++ b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/config/AwsClientOption.java
@@ -18,6 +18,7 @@
import software.amazon.awssdk.annotations.SdkProtectedApi;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.awscore.client.builder.AwsClientBuilder;
+import software.amazon.awssdk.awscore.defaultsmode.DefaultsMode;
import software.amazon.awssdk.core.client.config.ClientOption;
import software.amazon.awssdk.regions.Region;
@@ -65,6 +66,11 @@ public final class AwsClientOption extends ClientOption {
*/
public static final AwsClientOption ENDPOINT_PREFIX = new AwsClientOption<>(String.class);
+ /**
+ * Option to specify the {@link DefaultsMode}
+ */
+ public static final AwsClientOption DEFAULTS_MODE = new AwsClientOption<>(DefaultsMode.class);
+
private AwsClientOption(Class valueClass) {
super(valueClass);
}
diff --git a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/endpoint/DefaultServiceEndpointBuilder.java b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/endpoint/DefaultServiceEndpointBuilder.java
index fc1fc751a4e7..a0113969a810 100644
--- a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/endpoint/DefaultServiceEndpointBuilder.java
+++ b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/endpoint/DefaultServiceEndpointBuilder.java
@@ -18,7 +18,9 @@
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.function.Supplier;
import software.amazon.awssdk.annotations.NotThreadSafe;
import software.amazon.awssdk.annotations.SdkProtectedApi;
@@ -29,6 +31,7 @@
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.regions.ServiceEndpointKey;
import software.amazon.awssdk.regions.ServiceMetadata;
+import software.amazon.awssdk.regions.ServiceMetadataAdvancedOption;
import software.amazon.awssdk.utils.Lazy;
import software.amazon.awssdk.utils.Validate;
@@ -46,6 +49,7 @@ public final class DefaultServiceEndpointBuilder {
private Region region;
private Supplier profileFile;
private String profileName;
+ private final Map, Object> advancedOptions = new HashMap<>();
private Boolean dualstackEnabled;
private Boolean fipsEnabled;
@@ -77,6 +81,11 @@ public DefaultServiceEndpointBuilder withProfileName(String profileName) {
return this;
}
+ public DefaultServiceEndpointBuilder putAdvancedOption(ServiceMetadataAdvancedOption option, T value) {
+ advancedOptions.put(option, value);
+ return this;
+ }
+
public DefaultServiceEndpointBuilder withDualstackEnabled(Boolean dualstackEnabled) {
this.dualstackEnabled = dualstackEnabled;
return this;
@@ -126,7 +135,8 @@ public URI getServiceEndpoint() {
ServiceMetadata serviceMetadata = ServiceMetadata.of(serviceName)
.reconfigure(c -> c.profileFile(profileFile)
- .profileName(profileName));
+ .profileName(profileName)
+ .advancedOptions(advancedOptions));
URI endpoint = addProtocolToServiceEndpoint(serviceMetadata.endpointFor(ServiceEndpointKey.builder()
.region(region)
.tags(endpointTags)
diff --git a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/internal/defaultsmode/AutoDefaultsModeDiscovery.java b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/internal/defaultsmode/AutoDefaultsModeDiscovery.java
new file mode 100644
index 000000000000..5adc6ab7a092
--- /dev/null
+++ b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/internal/defaultsmode/AutoDefaultsModeDiscovery.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 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 software.amazon.awssdk.awscore.internal.defaultsmode;
+
+import java.util.Optional;
+import software.amazon.awssdk.annotations.SdkInternalApi;
+import software.amazon.awssdk.awscore.defaultsmode.DefaultsMode;
+import software.amazon.awssdk.core.SdkSystemSetting;
+import software.amazon.awssdk.regions.Region;
+import software.amazon.awssdk.regions.internal.util.EC2MetadataUtils;
+import software.amazon.awssdk.utils.JavaSystemSetting;
+import software.amazon.awssdk.utils.OptionalUtils;
+import software.amazon.awssdk.utils.SystemSetting;
+import software.amazon.awssdk.utils.internal.SystemSettingUtils;
+
+/**
+ * This class attempts to discover the appropriate {@link DefaultsMode} by inspecting the environment. It falls
+ * back to the {@link DefaultsMode#STANDARD} mode if the target mode cannot be determined.
+ */
+@SdkInternalApi
+public class AutoDefaultsModeDiscovery {
+ private static final String EC2_METADATA_REGION_PATH = "/latest/meta-data/placement/region";
+ private static final DefaultsMode FALLBACK_DEFAULTS_MODE = DefaultsMode.STANDARD;
+ private static final String ANDROID_JAVA_VENDOR = "The Android Project";
+ private static final String AWS_DEFAULT_REGION_ENV_VAR = "AWS_DEFAULT_REGION";
+
+ /**
+ * Discovers the defaultMode using the following workflow:
+ *
+ * 1. Check if it's on mobile
+ * 2. If it's not on mobile (best we can tell), see if we can determine whether we're an in-region or cross-region client.
+ * 3. If we couldn't figure out the region from environment variables. Check IMDSv2. This step might take up to 1 second
+ * (default connect timeout)
+ * 4. Finally, use fallback mode
+ */
+ public DefaultsMode discover(Region regionResolvedFromSdkClient) {
+
+ if (isMobile()) {
+ return DefaultsMode.MOBILE;
+ }
+
+ if (isAwsExecutionEnvironment()) {
+ Optional regionStr = regionFromAwsExecutionEnvironment();
+
+ if (regionStr.isPresent()) {
+ return compareRegion(regionStr.get(), regionResolvedFromSdkClient);
+ }
+ }
+
+ Optional regionFromEc2 = queryImdsV2();
+ if (regionFromEc2.isPresent()) {
+ return compareRegion(regionFromEc2.get(), regionResolvedFromSdkClient);
+ }
+
+ return FALLBACK_DEFAULTS_MODE;
+ }
+
+ private static DefaultsMode compareRegion(String region, Region clientRegion) {
+ if (region.equalsIgnoreCase(clientRegion.id())) {
+ return DefaultsMode.IN_REGION;
+ }
+
+ return DefaultsMode.CROSS_REGION;
+ }
+
+ private static Optional queryImdsV2() {
+ try {
+ String ec2InstanceRegion = EC2MetadataUtils.fetchData(EC2_METADATA_REGION_PATH, false, 1);
+ // ec2InstanceRegion could be null
+ return Optional.ofNullable(ec2InstanceRegion);
+ } catch (Exception exception) {
+ return Optional.empty();
+ }
+ }
+
+ /**
+ * Check to see if the application is running on a mobile device by verifying the Java
+ * vendor system property. Currently only checks for Android. While it's technically possible to
+ * use Java with iOS, it's not a common use-case.
+ *
+ * https://developer.android.com/reference/java/lang/System#getProperties()
+ */
+ private static boolean isMobile() {
+ return JavaSystemSetting.JAVA_VENDOR.getStringValue()
+ .filter(o -> o.equals(ANDROID_JAVA_VENDOR))
+ .isPresent();
+ }
+
+ private static boolean isAwsExecutionEnvironment() {
+ return SdkSystemSetting.AWS_EXECUTION_ENV.getStringValue().isPresent();
+ }
+
+ private static Optional regionFromAwsExecutionEnvironment() {
+ Optional regionFromRegionEnvVar = SdkSystemSetting.AWS_REGION.getStringValue();
+ return OptionalUtils.firstPresent(regionFromRegionEnvVar,
+ () -> SystemSettingUtils.resolveEnvironmentVariable(new DefaultRegionEnvVar()));
+ }
+
+ private static final class DefaultRegionEnvVar implements SystemSetting {
+ @Override
+ public String property() {
+ return null;
+ }
+
+ @Override
+ public String environmentVariable() {
+ return AWS_DEFAULT_REGION_ENV_VAR;
+ }
+
+ @Override
+ public String defaultValue() {
+ return null;
+ }
+ }
+}
diff --git a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/internal/defaultsmode/DefaultsModeResolver.java b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/internal/defaultsmode/DefaultsModeResolver.java
new file mode 100644
index 000000000000..c20ceebd04f9
--- /dev/null
+++ b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/internal/defaultsmode/DefaultsModeResolver.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 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 software.amazon.awssdk.awscore.internal.defaultsmode;
+
+import java.util.Locale;
+import java.util.Optional;
+import java.util.function.Supplier;
+import software.amazon.awssdk.annotations.SdkInternalApi;
+import software.amazon.awssdk.awscore.defaultsmode.DefaultsMode;
+import software.amazon.awssdk.core.SdkSystemSetting;
+import software.amazon.awssdk.core.retry.RetryMode;
+import software.amazon.awssdk.profiles.ProfileFile;
+import software.amazon.awssdk.profiles.ProfileFileSystemSetting;
+import software.amazon.awssdk.profiles.ProfileProperty;
+import software.amazon.awssdk.utils.OptionalUtils;
+
+/**
+ * Allows customizing the variables used during determination of a {@link DefaultsMode}. Created via {@link #create()}.
+ */
+@SdkInternalApi
+public final class DefaultsModeResolver {
+
+ private static final DefaultsMode SDK_DEFAULT_DEFAULTS_MODE = DefaultsMode.LEGACY;
+ private Supplier profileFile;
+ private String profileName;
+ private DefaultsMode mode;
+
+ private DefaultsModeResolver() {
+ }
+
+ public static DefaultsModeResolver create() {
+ return new DefaultsModeResolver();
+ }
+
+ /**
+ * Configure the profile file that should be used when determining the {@link RetryMode}. The supplier is only consulted
+ * if a higher-priority determinant (e.g. environment variables) does not find the setting.
+ */
+ public DefaultsModeResolver profileFile(Supplier profileFile) {
+ this.profileFile = profileFile;
+ return this;
+ }
+
+ /**
+ * Configure the profile file name should be used when determining the {@link RetryMode}.
+ */
+ public DefaultsModeResolver profileName(String profileName) {
+ this.profileName = profileName;
+ return this;
+ }
+
+ /**
+ * Configure the {@link DefaultsMode} that should be used if the mode is not specified anywhere else.
+ */
+ public DefaultsModeResolver defaultMode(DefaultsMode mode) {
+ this.mode = mode;
+ return this;
+ }
+
+ /**
+ * Resolve which defaults mode should be used, based on the configured values.
+ */
+ public DefaultsMode resolve() {
+ return OptionalUtils.firstPresent(DefaultsModeResolver.fromSystemSettings(), () -> fromProfileFile(profileFile,
+ profileName))
+ .orElseGet(this::fromDefaultMode);
+ }
+
+ private static Optional fromSystemSettings() {
+ return SdkSystemSetting.AWS_DEFAULTS_MODE.getStringValue()
+ .map(value -> DefaultsMode.fromValue(value.toLowerCase(Locale.US)));
+ }
+
+ private static Optional fromProfileFile(Supplier profileFile, String profileName) {
+ profileFile = profileFile != null ? profileFile : ProfileFile::defaultProfileFile;
+ profileName = profileName != null ? profileName : ProfileFileSystemSetting.AWS_PROFILE.getStringValueOrThrow();
+ return profileFile.get()
+ .profile(profileName)
+ .flatMap(p -> p.property(ProfileProperty.DEFAULTS_MODE))
+ .map(value -> DefaultsMode.fromValue(value.toLowerCase(Locale.US)));
+ }
+
+ private DefaultsMode fromDefaultMode() {
+ return mode != null ? mode : SDK_DEFAULT_DEFAULTS_MODE;
+ }
+}
diff --git a/core/aws-core/src/main/resources/software/amazon/awssdk/awscore/internal/defaults/sdk-default-configuration.json b/core/aws-core/src/main/resources/software/amazon/awssdk/awscore/internal/defaults/sdk-default-configuration.json
new file mode 100644
index 000000000000..3db13b26cc5e
--- /dev/null
+++ b/core/aws-core/src/main/resources/software/amazon/awssdk/awscore/internal/defaults/sdk-default-configuration.json
@@ -0,0 +1,55 @@
+{
+ "version": 1,
+ "base": {
+ "retryMode": "standard",
+ "stsRegionalEndpoints": "regional",
+ "s3UsEast1RegionalEndpoints": "regional",
+ "connectTimeoutInMillis": 1100,
+ "tlsNegotiationTimeoutInMillis": 1100
+ },
+ "modes": {
+ "standard": {
+ "connectTimeoutInMillis": {
+ "override": 3100
+ },
+ "tlsNegotiationTimeoutInMillis": {
+ "override": 3100
+ }
+ },
+ "in-region": {
+ },
+ "cross-region": {
+ "connectTimeoutInMillis": {
+ "override": 3100
+ },
+ "tlsNegotiationTimeoutInMillis": {
+ "override": 3100
+ }
+ },
+ "mobile": {
+ "connectTimeoutInMillis": {
+ "override": 30000
+ },
+ "tlsNegotiationTimeoutInMillis": {
+ "override": 30000
+ }
+ }
+ },
+ "documentation": {
+ "modes": {
+ "standard": "
The STANDARD mode provides the latest recommended default values that should be safe to run in most scenarios
Note that the default values vended from this mode might change as best practices may evolve. As a result, it is encouraged to perform tests when upgrading the SDK
",
+ "in-region": "
The IN_REGION mode builds on the standard mode and includes optimization tailored for applications which call AWS services from within the same AWS region
Note that the default values vended from this mode might change as best practices may evolve. As a result, it is encouraged to perform tests when upgrading the SDK
",
+ "cross-region": "
The CROSS_REGION mode builds on the standard mode and includes optimization tailored for applications which call AWS services in a different region
Note that the default values vended from this mode might change as best practices may evolve. As a result, it is encouraged to perform tests when upgrading the SDK
",
+ "mobile": "
The MOBILE mode builds on the standard mode and includes optimization tailored for mobile applications
Note that the default values vended from this mode might change as best practices may evolve. As a result, it is encouraged to perform tests when upgrading the SDK
",
+ "auto": "
The AUTO mode is an experimental mode that builds on the standard mode. The SDK will attempt to discover the execution environment to determine the appropriate settings automatically.
Note that the auto detection is heuristics-based and does not guarantee 100% accuracy. STANDARD mode will be used if the execution environment cannot be determined. The auto detection might query EC2 Instance Metadata service, which might introduce latency. Therefore we recommend choosing an explicit defaults_mode instead if startup latency is critical to your application
",
+ "legacy": "
The LEGACY mode provides default settings that vary per SDK and were used prior to establishment of defaults_mode
"
+ },
+ "configuration": {
+ "retryMode": "
A retry mode specifies how the SDK attempts retries. See Retry Mode
",
+ "stsRegionalEndpoints": "
Specifies how the SDK determines the AWS service endpoint that it uses to talk to the AWS Security Token Service (AWS STS). See Setting STS Regional endpoints
",
+ "s3UsEast1RegionalEndpoints": "
Specifies how the SDK determines the AWS service endpoint that it uses to talk to the Amazon S3 for the us-east-1 region
",
+ "connectTimeoutInMillis": "
The amount of time after making an initial connection attempt on a socket, where if the client does not receive a completion of the connect handshake, the client gives up and fails the operation
",
+ "tlsNegotiationTimeoutInMillis": "
The maximum amount of time that a TLS handshake is allowed to take from the time the CLIENT HELLO message is sent to ethe time the client and server have fully negotiated ciphers and exchanged keys
"
+ }
+ }
+}
\ No newline at end of file
diff --git a/core/aws-core/src/test/java/software/amazon/awssdk/awscore/client/builder/DefaultAwsClientBuilderTest.java b/core/aws-core/src/test/java/software/amazon/awssdk/awscore/client/builder/DefaultAwsClientBuilderTest.java
index c32600c5f014..227e406ceac4 100644
--- a/core/aws-core/src/test/java/software/amazon/awssdk/awscore/client/builder/DefaultAwsClientBuilderTest.java
+++ b/core/aws-core/src/test/java/software/amazon/awssdk/awscore/client/builder/DefaultAwsClientBuilderTest.java
@@ -42,7 +42,8 @@
import org.mockito.runners.MockitoJUnitRunner;
import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider;
import software.amazon.awssdk.auth.signer.Aws4Signer;
-import software.amazon.awssdk.awscore.client.config.AwsClientOption;
+import software.amazon.awssdk.awscore.defaultsmode.DefaultsMode;
+import software.amazon.awssdk.awscore.internal.defaultsmode.AutoDefaultsModeDiscovery;
import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration;
import software.amazon.awssdk.core.client.config.SdkClientConfiguration;
import software.amazon.awssdk.core.client.config.SdkClientOption;
@@ -76,10 +77,14 @@ public class DefaultAwsClientBuilderTest {
@Mock
private SdkAsyncHttpClient.Builder defaultAsyncHttpClientFactory;
+ @Mock
+ private AutoDefaultsModeDiscovery autoModeDiscovery;
+
@Before
public void setup() {
when(defaultHttpClientBuilder.buildWithDefaults(any())).thenReturn(mock(SdkHttpClient.class));
when(defaultAsyncHttpClientFactory.buildWithDefaults(any())).thenReturn(mock(SdkAsyncHttpClient.class));
+ when(autoModeDiscovery.discover(any())).thenReturn(DefaultsMode.IN_REGION);
}
@Test
@@ -232,7 +237,7 @@ private class TestClientBuilder extends AwsDefaultClientBuilder {
public TestClientBuilder() {
- super(defaultHttpClientBuilder, null);
+ super(defaultHttpClientBuilder, null, autoModeDiscovery);
}
@Override
@@ -273,7 +278,7 @@ private class TestAsyncClientBuilder extends AwsDefaultClientBuilder {
public TestAsyncClientBuilder() {
- super(defaultHttpClientBuilder, defaultAsyncHttpClientFactory);
+ super(defaultHttpClientBuilder, defaultAsyncHttpClientFactory, autoModeDiscovery);
}
@Override
diff --git a/core/aws-core/src/test/java/software/amazon/awssdk/awscore/client/builder/DefaultsModeTest.java b/core/aws-core/src/test/java/software/amazon/awssdk/awscore/client/builder/DefaultsModeTest.java
new file mode 100644
index 000000000000..9565b93bb14d
--- /dev/null
+++ b/core/aws-core/src/test/java/software/amazon/awssdk/awscore/client/builder/DefaultsModeTest.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright 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 software.amazon.awssdk.awscore.client.builder;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static software.amazon.awssdk.awscore.client.config.AwsAdvancedClientOption.ENABLE_DEFAULT_REGION_DETECTION;
+import static software.amazon.awssdk.awscore.client.config.AwsClientOption.DEFAULTS_MODE;
+import static software.amazon.awssdk.core.client.config.SdkClientOption.DEFAULT_RETRY_MODE;
+import static software.amazon.awssdk.core.client.config.SdkClientOption.RETRY_POLICY;
+import static software.amazon.awssdk.regions.ServiceMetadataAdvancedOption.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT;
+
+import java.time.Duration;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider;
+import software.amazon.awssdk.awscore.defaultsmode.DefaultsMode;
+import software.amazon.awssdk.awscore.internal.defaultsmode.AutoDefaultsModeDiscovery;
+import software.amazon.awssdk.awscore.internal.defaultsmode.DefaultsModeConfiguration;
+import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration;
+import software.amazon.awssdk.core.client.config.SdkClientConfiguration;
+import software.amazon.awssdk.core.retry.RetryMode;
+import software.amazon.awssdk.http.SdkHttpClient;
+import software.amazon.awssdk.http.SdkHttpConfigurationOption;
+import software.amazon.awssdk.http.async.SdkAsyncHttpClient;
+import software.amazon.awssdk.regions.Region;
+import software.amazon.awssdk.utils.AttributeMap;
+
+@RunWith(MockitoJUnitRunner.class)
+public class DefaultsModeTest {
+
+ private static final AttributeMap SERVICE_DEFAULTS = AttributeMap
+ .builder()
+ .put(SdkHttpConfigurationOption.READ_TIMEOUT, Duration.ofSeconds(10))
+ .build();
+
+ private static final String ENDPOINT_PREFIX = "test";
+ private static final String SIGNING_NAME = "test";
+ private static final String SERVICE_NAME = "test";
+
+ @Mock
+ private SdkHttpClient.Builder defaultHttpClientBuilder;
+
+ @Mock
+ private SdkAsyncHttpClient.Builder defaultAsyncHttpClientBuilder;
+
+ @Mock
+ private AutoDefaultsModeDiscovery autoModeDiscovery;
+
+ @Test
+ public void defaultClient_shouldUseLegacyModeWithExistingDefaults() {
+ TestClient client = testClientBuilder()
+ .region(Region.US_WEST_2)
+ .httpClientBuilder((SdkHttpClient.Builder) serviceDefaults -> {
+ assertThat(serviceDefaults).isEqualTo(SERVICE_DEFAULTS);
+ return mock(SdkHttpClient.class);
+ })
+ .build();
+
+ assertThat(client.clientConfiguration.option(DEFAULTS_MODE)).isEqualTo(DefaultsMode.LEGACY);
+ assertThat(client.clientConfiguration.option(RETRY_POLICY).retryMode()).isEqualTo(RetryMode.defaultRetryMode());
+ assertThat(client.clientConfiguration.option(DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT)).isNull();
+ }
+
+ @Test
+ public void nonLegacyDefaultsMode_shouldApplySdkDefaultsAndHttpDefaults() {
+ DefaultsMode targetMode = DefaultsMode.IN_REGION;
+
+ TestClient client =
+ testClientBuilder().region(Region.US_WEST_1)
+ .defaultsMode(targetMode)
+ .httpClientBuilder((SdkHttpClient.Builder) serviceDefaults -> {
+ AttributeMap defaultHttpConfig = DefaultsModeConfiguration.defaultHttpConfig(targetMode);
+ AttributeMap mergedDefaults = SERVICE_DEFAULTS.merge(defaultHttpConfig);
+ assertThat(serviceDefaults).isEqualTo(mergedDefaults);
+ return mock(SdkHttpClient.class);
+ }).build();
+
+ assertThat(client.clientConfiguration.option(DEFAULTS_MODE)).isEqualTo(targetMode);
+
+ AttributeMap attributes = DefaultsModeConfiguration.defaultConfig(targetMode);
+
+ assertThat(client.clientConfiguration.option(RETRY_POLICY).retryMode()).isEqualTo(attributes.get(DEFAULT_RETRY_MODE));
+ assertThat(client.clientConfiguration.option(DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT)).isEqualTo("regional");
+ }
+
+ @Test
+ public void nonLegacyDefaultsModeAsyncClient_shouldApplySdkDefaultsAndHttpDefaults() {
+ DefaultsMode targetMode = DefaultsMode.IN_REGION;
+
+ TestAsyncClient client =
+ testAsyncClientBuilder().region(Region.US_WEST_1)
+ .defaultsMode(targetMode)
+ .httpClientBuilder((SdkHttpClient.Builder) serviceDefaults -> {
+ AttributeMap defaultHttpConfig = DefaultsModeConfiguration.defaultHttpConfig(targetMode);
+ AttributeMap mergedDefaults = SERVICE_DEFAULTS.merge(defaultHttpConfig);
+ assertThat(serviceDefaults).isEqualTo(mergedDefaults);
+ return mock(SdkHttpClient.class);
+ }).build();
+
+ assertThat(client.clientConfiguration.option(DEFAULTS_MODE)).isEqualTo(targetMode);
+
+ AttributeMap attributes = DefaultsModeConfiguration.defaultConfig(targetMode);
+
+ assertThat(client.clientConfiguration.option(RETRY_POLICY).retryMode()).isEqualTo(attributes.get(DEFAULT_RETRY_MODE));
+ }
+
+ @Test
+ public void clientOverrideRetryMode_shouldTakePrecedence() {
+ TestClient client =
+ testClientBuilder().region(Region.US_WEST_1)
+ .defaultsMode(DefaultsMode.IN_REGION)
+ .overrideConfiguration(o -> o.retryPolicy(RetryMode.LEGACY))
+ .build();
+ assertThat(client.clientConfiguration.option(DEFAULTS_MODE)).isEqualTo(DefaultsMode.IN_REGION);
+ assertThat(client.clientConfiguration.option(RETRY_POLICY).retryMode()).isEqualTo(RetryMode.LEGACY);
+ }
+
+ @Test
+ public void autoMode_shouldResolveDefaultsMode() {
+ DefaultsMode expectedMode = DefaultsMode.IN_REGION;
+ when(autoModeDiscovery.discover(any(Region.class))).thenReturn(expectedMode);
+ TestClient client =
+ testClientBuilder().region(Region.US_WEST_1)
+ .defaultsMode(DefaultsMode.AUTO)
+ .build();
+
+ assertThat(client.clientConfiguration.option(DEFAULTS_MODE)).isEqualTo(expectedMode);
+ }
+
+ private static class TestClient {
+ private final SdkClientConfiguration clientConfiguration;
+
+ public TestClient(SdkClientConfiguration clientConfiguration) {
+ this.clientConfiguration = clientConfiguration;
+ }
+ }
+
+ private AwsClientBuilder testClientBuilder() {
+ ClientOverrideConfiguration overrideConfig =
+ ClientOverrideConfiguration.builder()
+ .putAdvancedOption(ENABLE_DEFAULT_REGION_DETECTION, false)
+ .build();
+
+ return new TestClientBuilder().credentialsProvider(AnonymousCredentialsProvider.create())
+ .overrideConfiguration(overrideConfig);
+ }
+
+ private AwsClientBuilder testAsyncClientBuilder() {
+ ClientOverrideConfiguration overrideConfig =
+ ClientOverrideConfiguration.builder()
+ .putAdvancedOption(ENABLE_DEFAULT_REGION_DETECTION, false)
+ .build();
+
+ return new TestAsyncClientBuilder().credentialsProvider(AnonymousCredentialsProvider.create())
+ .overrideConfiguration(overrideConfig);
+ }
+
+ private class TestClientBuilder extends AwsDefaultClientBuilder
+ implements AwsClientBuilder {
+
+ public TestClientBuilder() {
+ super(defaultHttpClientBuilder, defaultAsyncHttpClientBuilder, autoModeDiscovery);
+ }
+
+ @Override
+ protected TestClient buildClient() {
+ return new TestClient(super.syncClientConfiguration());
+ }
+
+ @Override
+ protected String serviceEndpointPrefix() {
+ return ENDPOINT_PREFIX;
+ }
+
+ @Override
+ protected String signingName() {
+ return SIGNING_NAME;
+ }
+
+ @Override
+ protected String serviceName() {
+ return SERVICE_NAME;
+ }
+
+ @Override
+ protected AttributeMap serviceHttpConfig() {
+ return SERVICE_DEFAULTS;
+ }
+ }
+
+ private class TestAsyncClientBuilder extends AwsDefaultClientBuilder
+ implements AwsClientBuilder {
+
+ public TestAsyncClientBuilder() {
+ super(defaultHttpClientBuilder, defaultAsyncHttpClientBuilder, autoModeDiscovery);
+ }
+
+ @Override
+ protected TestAsyncClient buildClient() {
+ return new TestAsyncClient(super.asyncClientConfiguration());
+ }
+
+ @Override
+ protected String serviceEndpointPrefix() {
+ return ENDPOINT_PREFIX;
+ }
+
+ @Override
+ protected String signingName() {
+ return SIGNING_NAME;
+ }
+
+ @Override
+ protected String serviceName() {
+ return SERVICE_NAME;
+ }
+
+ @Override
+ protected AttributeMap serviceHttpConfig() {
+ return SERVICE_DEFAULTS;
+ }
+ }
+
+ private static class TestAsyncClient {
+ private final SdkClientConfiguration clientConfiguration;
+
+ private TestAsyncClient(SdkClientConfiguration clientConfiguration) {
+ this.clientConfiguration = clientConfiguration;
+ }
+ }
+}
diff --git a/core/aws-core/src/test/java/software/amazon/awssdk/awscore/client/endpoint/DefaultServiceEndpointBuilderTest.java b/core/aws-core/src/test/java/software/amazon/awssdk/awscore/client/endpoint/DefaultServiceEndpointBuilderTest.java
index 90fa1f40034d..38b65f87a653 100644
--- a/core/aws-core/src/test/java/software/amazon/awssdk/awscore/client/endpoint/DefaultServiceEndpointBuilderTest.java
+++ b/core/aws-core/src/test/java/software/amazon/awssdk/awscore/client/endpoint/DefaultServiceEndpointBuilderTest.java
@@ -20,6 +20,7 @@
import org.junit.Test;
import software.amazon.awssdk.awscore.endpoint.DefaultServiceEndpointBuilder;
import software.amazon.awssdk.regions.Region;
+import software.amazon.awssdk.regions.ServiceMetadataAdvancedOption;
public class DefaultServiceEndpointBuilderTest {
@@ -43,4 +44,12 @@ public void getServiceEndpoint_S3NonStandardRegion_HttpProtocol() throws Excepti
.withRegion(Region.EU_CENTRAL_1);
assertEquals("http://s3.eu-central-1.amazonaws.com", endpointBuilder.getServiceEndpoint().toString());
}
+
+ @Test
+ public void getServiceEndpoint_regionalOption_shouldUseRegionalEndpoint() throws Exception {
+ DefaultServiceEndpointBuilder endpointBuilder = new DefaultServiceEndpointBuilder("s3", "http")
+ .withRegion(Region.US_EAST_1).putAdvancedOption(ServiceMetadataAdvancedOption.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT,
+ "regional");
+ assertEquals("http://s3.us-east-1.amazonaws.com", endpointBuilder.getServiceEndpoint().toString());
+ }
}
diff --git a/core/aws-core/src/test/java/software/amazon/awssdk/awscore/internal/defaultsmode/AutoDefaultsModeDiscoveryTest.java b/core/aws-core/src/test/java/software/amazon/awssdk/awscore/internal/defaultsmode/AutoDefaultsModeDiscoveryTest.java
new file mode 100644
index 000000000000..0170dfc39da4
--- /dev/null
+++ b/core/aws-core/src/test/java/software/amazon/awssdk/awscore/internal/defaultsmode/AutoDefaultsModeDiscoveryTest.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright 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 software.amazon.awssdk.awscore.internal.defaultsmode;
+
+import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
+import static com.github.tomakehurst.wiremock.client.WireMock.get;
+import static com.github.tomakehurst.wiremock.client.WireMock.put;
+import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import com.github.tomakehurst.wiremock.junit.WireMockRule;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.concurrent.Callable;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import software.amazon.awssdk.awscore.defaultsmode.DefaultsMode;
+import software.amazon.awssdk.core.SdkSystemSetting;
+import software.amazon.awssdk.regions.Region;
+import software.amazon.awssdk.regions.internal.util.EC2MetadataUtils;
+import software.amazon.awssdk.testutils.EnvironmentVariableHelper;
+import software.amazon.awssdk.utils.JavaSystemSetting;
+
+@RunWith(Parameterized.class)
+public class AutoDefaultsModeDiscoveryTest {
+ private static final EnvironmentVariableHelper ENVIRONMENT_VARIABLE_HELPER = new EnvironmentVariableHelper();
+ @Parameterized.Parameter
+ public TestData testData;
+
+ @Parameterized.Parameters
+ public static Collection