From d3dc681e3642cad2fd0c200b750eb853ece4639f Mon Sep 17 00:00:00 2001 From: Bennett Lynch Date: Mon, 2 Aug 2021 16:44:48 -0700 Subject: [PATCH] Add customization.config support for setting default RetryMode This change allows for the SDK's default RetryMode to be configurable via setting an SdkClientOption as part of the ClientBuilder's `mergeInternalDefaults` logic, or via setting the `defaultRetryMode` variable in an accompanying customization.config file. --- .../feature-AWSSDKforJavav2-36a3de6.json | 6 +++ .../customization/CustomizationConfig.java | 16 +++++++ .../poet/builder/BaseClientBuilderClass.java | 30 +++++++++---- .../poet/builder/BuilderClassTest.java | 2 +- ...ient-builder-internal-defaults-class.java} | 6 ++- .../c2j/internalconfig/customization.config | 3 +- .../builder/AwsDefaultClientBuilder.java | 1 + .../builder/SdkDefaultClientBuilder.java | 1 + .../core/client/config/SdkClientOption.java | 8 ++++ .../amazon/awssdk/core/retry/RetryMode.java | 17 ++++++- .../awssdk/core/retry/RetryModeTest.java | 45 ++++++++++--------- .../dynamodb/DynamoDbRetryPolicy.java | 1 + .../services/kinesis/KinesisRetryPolicy.java | 1 + 13 files changed, 104 insertions(+), 33 deletions(-) create mode 100644 .changes/next-release/feature-AWSSDKforJavav2-36a3de6.json rename codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/{test-client-builder-internal-user-agent-class.java => test-client-builder-internal-defaults-class.java} (90%) diff --git a/.changes/next-release/feature-AWSSDKforJavav2-36a3de6.json b/.changes/next-release/feature-AWSSDKforJavav2-36a3de6.json new file mode 100644 index 000000000000..404657d7af3f --- /dev/null +++ b/.changes/next-release/feature-AWSSDKforJavav2-36a3de6.json @@ -0,0 +1,6 @@ +{ + "category": "AWS SDK for Java v2", + "contributor": "", + "type": "feature", + "description": "Add customization.config support for setting default RetryMode" +} diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/model/config/customization/CustomizationConfig.java b/codegen/src/main/java/software/amazon/awssdk/codegen/model/config/customization/CustomizationConfig.java index 6707d63d68b9..518056266be0 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/model/config/customization/CustomizationConfig.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/model/config/customization/CustomizationConfig.java @@ -19,9 +19,15 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import software.amazon.awssdk.core.retry.RetryMode; import software.amazon.awssdk.core.traits.PayloadTrait; import software.amazon.awssdk.utils.AttributeMap; +/** + * {@code service-2.json} models can be manually modified via defining properties in an associated {@code customization.config} + * file. This class defines the Java bean representation that will be used to parse the JSON customization file. The bean can + * then be later queried in the misc. codegen steps. + */ public class CustomizationConfig { /** @@ -188,6 +194,8 @@ public class CustomizationConfig { private UnderscoresInNameBehavior underscoresInNameBehavior; private String userAgent; + + private RetryMode defaultRetryMode; private CustomizationConfig() { } @@ -485,4 +493,12 @@ public CustomizationConfig withUserAgent(String userAgent) { this.userAgent = userAgent; return this; } + + public RetryMode getDefaultRetryMode() { + return defaultRetryMode; + } + + public void setDefaultRetryMode(RetryMode defaultRetryMode) { + this.defaultRetryMode = defaultRetryMode; + } } diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderClass.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderClass.java index 4d141cd594ff..69b6e4500c45 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderClass.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderClass.java @@ -29,6 +29,7 @@ import com.squareup.javapoet.TypeVariableName; import java.util.Collections; import java.util.List; +import java.util.Optional; import javax.lang.model.element.Modifier; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.auth.signer.Aws4Signer; @@ -45,6 +46,7 @@ import software.amazon.awssdk.core.endpointdiscovery.providers.DefaultEndpointDiscoveryProviderChain; import software.amazon.awssdk.core.interceptor.ClasspathInterceptorChainFactory; import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; +import software.amazon.awssdk.core.retry.RetryMode; import software.amazon.awssdk.core.signer.Signer; import software.amazon.awssdk.http.Protocol; import software.amazon.awssdk.http.SdkHttpConfigurationOption; @@ -92,9 +94,7 @@ public TypeSpec poetSpec() { builder.addMethod(serviceNameMethod()); builder.addMethod(mergeServiceDefaultsMethod()); - if (model.getCustomizationConfig().getUserAgent() != null) { - builder.addMethod(mergeInternalDefaultsMethod()); - } + mergeInternalDefaultsMethod().ifPresent(builder::addMethod); builder.addMethod(finalizeServiceConfigurationMethod()); builder.addMethod(defaultSignerMethod()); @@ -175,19 +175,31 @@ private MethodSpec mergeServiceDefaultsMethod() { return builder.build(); } - private MethodSpec mergeInternalDefaultsMethod() { + private Optional mergeInternalDefaultsMethod() { String userAgent = model.getCustomizationConfig().getUserAgent(); + RetryMode defaultRetryMode = model.getCustomizationConfig().getDefaultRetryMode(); + + // If none of the options are customized, then we do not need to bother overriding the method + if (userAgent == null && defaultRetryMode == null) { + return Optional.empty(); + } MethodSpec.Builder builder = MethodSpec.methodBuilder("mergeInternalDefaults") .addAnnotation(Override.class) .addModifiers(PROTECTED, FINAL) .returns(SdkClientConfiguration.class) .addParameter(SdkClientConfiguration.class, "config") - .addCode("return config.merge(c -> c.option($T.INTERNAL_USER_AGENT, $S)\n", - SdkClientOption.class, userAgent); - - builder.addCode(");"); - return builder.build(); + .addCode("return config.merge(c -> {\n"); + if (userAgent != null) { + builder.addCode("c.option($T.INTERNAL_USER_AGENT, $S);\n", + SdkClientOption.class, userAgent); + } + if (defaultRetryMode != null) { + builder.addCode("c.option($T.DEFAULT_RETRY_MODE, $T.$L);\n", + SdkClientOption.class, RetryMode.class, defaultRetryMode.name()); + } + builder.addCode("});\n"); + return Optional.of(builder.build()); } private MethodSpec finalizeServiceConfigurationMethod() { diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/builder/BuilderClassTest.java b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/builder/BuilderClassTest.java index 120227faa29f..74c942f68cb6 100644 --- a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/builder/BuilderClassTest.java +++ b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/builder/BuilderClassTest.java @@ -40,7 +40,7 @@ public void baseClientBuilderClass() throws Exception { @Test public void baseClientBuilderClassWithInternalUserAgent() throws Exception { - assertThat(new BaseClientBuilderClass(ClientTestModels.internalConfigModels()), generatesTo("test-client-builder-internal-user-agent-class.java")); + assertThat(new BaseClientBuilderClass(ClientTestModels.internalConfigModels()), generatesTo("test-client-builder-internal-defaults-class.java")); } @Test diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-internal-user-agent-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-internal-defaults-class.java similarity index 90% rename from codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-internal-user-agent-class.java rename to codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-internal-defaults-class.java index 024ff4687e89..39f2c5697bb6 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-internal-user-agent-class.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-client-builder-internal-defaults-class.java @@ -10,6 +10,7 @@ import software.amazon.awssdk.core.client.config.SdkClientOption; import software.amazon.awssdk.core.interceptor.ClasspathInterceptorChainFactory; import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; +import software.amazon.awssdk.core.retry.RetryMode; import software.amazon.awssdk.core.signer.Signer; import software.amazon.awssdk.utils.CollectionUtils; @@ -37,7 +38,10 @@ protected final SdkClientConfiguration mergeServiceDefaults(SdkClientConfigurati @Override protected final SdkClientConfiguration mergeInternalDefaults(SdkClientConfiguration config) { - return config.merge(c -> c.option(SdkClientOption.INTERNAL_USER_AGENT, "md/foobar")); + return config.merge(c -> { + c.option(SdkClientOption.INTERNAL_USER_AGENT, "md/foobar"); + c.option(SdkClientOption.DEFAULT_RETRY_MODE, RetryMode.STANDARD); + }); } @Override diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/internalconfig/customization.config b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/internalconfig/customization.config index 687ea6c7bbad..b4e783add53d 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/internalconfig/customization.config +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/internalconfig/customization.config @@ -2,5 +2,6 @@ "authPolicyActions" : { "skip" : true }, - "userAgent": "md/foobar" + "userAgent": "md/foobar", + "defaultRetryMode": "STANDARD" } 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 72c1be77f3ad..4d1120a74392 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 @@ -235,6 +235,7 @@ private RetryPolicy resolveAwsRetryPolicy(SdkClientConfiguration config) { RetryMode retryMode = RetryMode.resolver() .profileFile(() -> config.option(SdkClientOption.PROFILE_FILE)) .profileName(config.option(SdkClientOption.PROFILE_NAME)) + .defaultRetryMode(config.option(SdkClientOption.DEFAULT_RETRY_MODE)) .resolve(); return AwsRetryPolicy.forRetryMode(retryMode); } diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/builder/SdkDefaultClientBuilder.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/builder/SdkDefaultClientBuilder.java index fa443f41d0f8..30af786e5ecc 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/builder/SdkDefaultClientBuilder.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/builder/SdkDefaultClientBuilder.java @@ -259,6 +259,7 @@ private RetryPolicy resolveRetryPolicy(SdkClientConfiguration config) { RetryMode retryMode = RetryMode.resolver() .profileFile(() -> config.option(SdkClientOption.PROFILE_FILE)) .profileName(config.option(SdkClientOption.PROFILE_NAME)) + .defaultRetryMode(config.option(SdkClientOption.DEFAULT_RETRY_MODE)) .resolve(); return RetryPolicy.forRetryMode(retryMode); } diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/config/SdkClientOption.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/config/SdkClientOption.java index fb8177d00e94..01a45c683b8a 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/config/SdkClientOption.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/config/SdkClientOption.java @@ -25,6 +25,7 @@ import software.amazon.awssdk.core.ServiceConfiguration; import software.amazon.awssdk.core.interceptor.ExecutionAttributes; import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; +import software.amazon.awssdk.core.retry.RetryMode; import software.amazon.awssdk.core.retry.RetryPolicy; import software.amazon.awssdk.http.SdkHttpClient; import software.amazon.awssdk.http.async.SdkAsyncHttpClient; @@ -149,6 +150,13 @@ public final class SdkClientOption extends ClientOption { */ public static final SdkClientOption INTERNAL_USER_AGENT = new SdkClientOption<>(String.class); + /** + * Option to specify the default retry mode. + * + * @see RetryMode.Resolver#defaultRetryMode(RetryMode) + */ + public static final SdkClientOption DEFAULT_RETRY_MODE = new SdkClientOption<>(RetryMode.class); + private SdkClientOption(Class valueClass) { super(valueClass); } diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/retry/RetryMode.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/retry/RetryMode.java index 458e777d589c..146d741077c9 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/retry/RetryMode.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/retry/RetryMode.java @@ -91,8 +91,11 @@ public static Resolver resolver() { * Allows customizing the variables used during determination of a {@link RetryMode}. Created via {@link #resolver()}. */ public static class Resolver { + private static final RetryMode SDK_DEFAULT_RETRY_MODE = LEGACY; + private Supplier profileFile; private String profileName; + private RetryMode defaultRetryMode; private Resolver() { } @@ -114,12 +117,20 @@ public Resolver profileName(String profileName) { return this; } + /** + * Configure the {@link RetryMode} that should be used if the mode is not specified anywhere else. + */ + public Resolver defaultRetryMode(RetryMode defaultRetryMode) { + this.defaultRetryMode = defaultRetryMode; + return this; + } + /** * Resolve which retry mode should be used, based on the configured values. */ public RetryMode resolve() { return OptionalUtils.firstPresent(Resolver.fromSystemSettings(), () -> fromProfileFile(profileFile, profileName)) - .orElse(RetryMode.LEGACY); + .orElseGet(this::fromDefaultMode); } private static Optional fromSystemSettings() { @@ -150,5 +161,9 @@ private static Optional fromString(String string) { throw new IllegalStateException("Unsupported retry policy mode configured: " + string); } } + + private RetryMode fromDefaultMode() { + return defaultRetryMode != null ? defaultRetryMode : SDK_DEFAULT_RETRY_MODE; + } } } diff --git a/core/sdk-core/src/test/java/software/amazon/awssdk/core/retry/RetryModeTest.java b/core/sdk-core/src/test/java/software/amazon/awssdk/core/retry/RetryModeTest.java index 4020e955bb21..cc9ea5511148 100644 --- a/core/sdk-core/src/test/java/software/amazon/awssdk/core/retry/RetryModeTest.java +++ b/core/sdk-core/src/test/java/software/amazon/awssdk/core/retry/RetryModeTest.java @@ -22,9 +22,9 @@ import java.nio.file.Paths; import java.util.Arrays; import java.util.Collection; +import java.util.concurrent.Callable; import org.junit.After; import org.junit.Before; -import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -44,25 +44,26 @@ public class RetryModeTest { public static Collection data() { return Arrays.asList(new Object[] { // Test defaults - new TestData(null, null, null, RetryMode.LEGACY), - new TestData(null, null, "PropertyNotSet", RetryMode.LEGACY), + new TestData(null, null, null, null, RetryMode.LEGACY), + new TestData(null, null, "PropertyNotSet", null, RetryMode.LEGACY), // Test precedence - new TestData("standard", "legacy", "PropertySetToLegacy", RetryMode.STANDARD), - new TestData("standard", null, null, RetryMode.STANDARD), - new TestData(null, "standard", "PropertySetToLegacy", RetryMode.STANDARD), - new TestData(null, "standard", null, RetryMode.STANDARD), - new TestData(null, null, "PropertySetToStandard", RetryMode.STANDARD), + new TestData("standard", "legacy", "PropertySetToLegacy", RetryMode.LEGACY, RetryMode.STANDARD), + new TestData("standard", null, null, RetryMode.LEGACY, RetryMode.STANDARD), + new TestData(null, "standard", "PropertySetToLegacy", RetryMode.LEGACY, RetryMode.STANDARD), + new TestData(null, "standard", null, RetryMode.LEGACY, RetryMode.STANDARD), + new TestData(null, null, "PropertySetToStandard", RetryMode.LEGACY, RetryMode.STANDARD), + new TestData(null, null, null, RetryMode.STANDARD, RetryMode.STANDARD), // Test invalid values - new TestData("wrongValue", null, null, null), - new TestData(null, "wrongValue", null, null), - new TestData(null, null, "PropertySetToUnsupportedValue", null), + new TestData("wrongValue", null, null, null, IllegalStateException.class), + new TestData(null, "wrongValue", null, null, IllegalStateException.class), + new TestData(null, null, "PropertySetToUnsupportedValue", null, IllegalStateException.class), // Test capitalization standardization - new TestData("sTaNdArD", null, null, RetryMode.STANDARD), - new TestData(null, "sTaNdArD", null, RetryMode.STANDARD), - new TestData(null, null, "PropertyMixedCase", RetryMode.STANDARD), + new TestData("sTaNdArD", null, null, null, RetryMode.STANDARD), + new TestData(null, "sTaNdArD", null, null, RetryMode.STANDARD), + new TestData(null, null, "PropertyMixedCase", null, RetryMode.STANDARD), }); } @@ -76,7 +77,7 @@ public void methodSetup() { } @Test - public void differentCombinationOfConfigs_shouldResolveCorrectly() { + public void differentCombinationOfConfigs_shouldResolveCorrectly() throws Exception { if (testData.envVarValue != null) { ENVIRONMENT_VARIABLE_HELPER.set(SdkSystemSetting.AWS_RETRY_MODE.environmentVariable(), testData.envVarValue); } @@ -92,10 +93,12 @@ public void differentCombinationOfConfigs_shouldResolveCorrectly() { System.setProperty(ProfileFileSystemSetting.AWS_CONFIG_FILE.property(), diskLocationForFile); } - if (testData.expected == null) { - assertThatThrownBy(RetryMode::defaultRetryMode).isInstanceOf(RuntimeException.class); + Callable result = RetryMode.resolver().defaultRetryMode(testData.defaultMode)::resolve; + if (testData.expected instanceof Class) { + Class expectedClassType = (Class) testData.expected; + assertThatThrownBy(result::call).isInstanceOf(expectedClassType); } else { - assertThat(RetryMode.defaultRetryMode()).isEqualTo(testData.expected); + assertThat(result.call()).isEqualTo(testData.expected); } } @@ -108,12 +111,14 @@ private static class TestData { private final String envVarValue; private final String systemProperty; private final String configFile; - private final RetryMode expected; + private final RetryMode defaultMode; + private final Object expected; - TestData(String systemProperty, String envVarValue, String configFile, RetryMode expected) { + TestData(String systemProperty, String envVarValue, String configFile, RetryMode defaultMode, Object expected) { this.envVarValue = envVarValue; this.systemProperty = systemProperty; this.configFile = configFile; + this.defaultMode = defaultMode; this.expected = expected; } } diff --git a/services/dynamodb/src/main/java/software/amazon/awssdk/services/dynamodb/DynamoDbRetryPolicy.java b/services/dynamodb/src/main/java/software/amazon/awssdk/services/dynamodb/DynamoDbRetryPolicy.java index b470e1b8a700..eb46ebf2261e 100644 --- a/services/dynamodb/src/main/java/software/amazon/awssdk/services/dynamodb/DynamoDbRetryPolicy.java +++ b/services/dynamodb/src/main/java/software/amazon/awssdk/services/dynamodb/DynamoDbRetryPolicy.java @@ -73,6 +73,7 @@ public static RetryPolicy resolveRetryPolicy(SdkClientConfiguration config) { RetryMode retryMode = RetryMode.resolver() .profileFile(() -> config.option(SdkClientOption.PROFILE_FILE)) .profileName(config.option(SdkClientOption.PROFILE_NAME)) + .defaultRetryMode(config.option(SdkClientOption.DEFAULT_RETRY_MODE)) .resolve(); switch (retryMode) { diff --git a/services/kinesis/src/main/java/software/amazon/awssdk/services/kinesis/KinesisRetryPolicy.java b/services/kinesis/src/main/java/software/amazon/awssdk/services/kinesis/KinesisRetryPolicy.java index 0bbdbc5dcc89..621993a91f4f 100644 --- a/services/kinesis/src/main/java/software/amazon/awssdk/services/kinesis/KinesisRetryPolicy.java +++ b/services/kinesis/src/main/java/software/amazon/awssdk/services/kinesis/KinesisRetryPolicy.java @@ -42,6 +42,7 @@ public static RetryPolicy resolveRetryPolicy(SdkClientConfiguration config) { RetryMode retryMode = RetryMode.resolver() .profileFile(() -> config.option(SdkClientOption.PROFILE_FILE)) .profileName(config.option(SdkClientOption.PROFILE_NAME)) + .defaultRetryMode(config.option(SdkClientOption.DEFAULT_RETRY_MODE)) .resolve(); return AwsRetryPolicy.forRetryMode(retryMode) .toBuilder()