diff --git a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/internal/AwsExecutionContextBuilder.java b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/internal/AwsExecutionContextBuilder.java index 067e48926990..32273f16019c 100644 --- a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/internal/AwsExecutionContextBuilder.java +++ b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/internal/AwsExecutionContextBuilder.java @@ -263,14 +263,14 @@ private static void putAuthSchemeResolutionAttributes(ExecutionAttributes execut SdkClientConfiguration clientConfig, SdkRequest originalRequest) { - // TODO(sra-identity-and-auth): When request-level auth scheme provider is added, use the request-level auth scheme - // provider if the customer specified an override, otherwise fall back to the one on the client. + // TODO(request-override auth scheme feature): When request-level auth scheme provider is added, use the request-level + // auth scheme provider if the customer specified an override, otherwise fall back to the one on the client. AuthSchemeProvider authSchemeProvider = clientConfig.option(SdkClientOption.AUTH_SCHEME_PROVIDER); // Use auth schemes that the user specified at the request level with // preference over those on the client. - // TODO(sra-identity-and-auth): The request level schemes should be "merged" with client level, with request preferred - // over client. + // TODO(request-override auth scheme feature): The request level schemes should be "merged" with client level, with + // request preferred over client. Map> authSchemes = clientConfig.option(SdkClientOption.AUTH_SCHEMES); IdentityProviders identityProviders = resolveIdentityProviders(originalRequest, clientConfig); diff --git a/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/internal/DefaultIdentityProviders.java b/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/internal/DefaultIdentityProviders.java index 863db0710b30..d28e8af4cb90 100644 --- a/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/internal/DefaultIdentityProviders.java +++ b/core/identity-spi/src/main/java/software/amazon/awssdk/identity/spi/internal/DefaultIdentityProviders.java @@ -36,10 +36,9 @@ @SdkInternalApi public final class DefaultIdentityProviders implements IdentityProviders { /** - * TODO(sra-identity-auth): Currently, some customers assume we won't interact with the identity providers when we create - * the client. This isn't true - we need to call identityType. To TEMPORARILY work around those customer's tests failing, - * this is marked lazy. Once we fully migrate over to the SRA as the default code path, we should remove this lazy and - * ticket everyone in live who is making those bad assumptions. + * Currently, some customers assume we won't interact with the identity providers when we create + * the client. This isn't true - we need to call identityType. To work around those customer's tests failing, + * this is marked lazy. See JAVA-8670 for more details. */ private final Lazy, IdentityProvider>> identityProviders; private final List> identityProvidersList; diff --git a/core/identity-spi/src/test/java/software/amazon/awssdk/identity/spi/IdentityProvidersTest.java b/core/identity-spi/src/test/java/software/amazon/awssdk/identity/spi/IdentityProvidersTest.java index 4a208607984c..37a582eea40f 100644 --- a/core/identity-spi/src/test/java/software/amazon/awssdk/identity/spi/IdentityProvidersTest.java +++ b/core/identity-spi/src/test/java/software/amazon/awssdk/identity/spi/IdentityProvidersTest.java @@ -99,7 +99,6 @@ public void copyBuilder_addIdentityProvider_works() { @Test public void identityProviders_notTouched_untilNeeded() { - // TODO(sra-identity-auth): This should be removed once everything is on useSraAuth = true IdentityProvider awsCredentialsProvider = Mockito.mock(IdentityProvider.class); IdentityProviders providers = IdentityProviders.builder() diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/RequestOverrideConfiguration.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/RequestOverrideConfiguration.java index ae6bd6fd85a5..d9afa70c5ba2 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/RequestOverrideConfiguration.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/RequestOverrideConfiguration.java @@ -489,21 +489,43 @@ default B putRawQueryParameter(String name, String value) { * @param plugins The list of plugins for this request. * @return This object for method chaining. */ - @SdkPreviewApi B plugins(List plugins); /** - * Add a plugin used to update the configuration used by this request. - * - * @param plugin The plugin to add. + * Adds a plugin that will modify the configuration used by this specific request. + * + *

Request-level plugins are applied at request execution time. Multiple plugins can be added and + * are executed in the order they were added. + * + *

Note: Request-level override configuration (e.g., {@link #apiCallTimeout(Duration)}) takes + * precedence over request-level plugin settings. This means if you set a value directly on the request + * override configuration and also set it via a plugin, the direct configuration value will be used. + * + *

Example: + * {@snippet : + * SdkPlugin highTimeoutPlugin = config -> { + * config.overrideConfiguration(c -> c.apiCallTimeout(Duration.ofMinutes(5))); + * }; + * + * GetObjectResponse response = s3Client.getObject( + * GetObjectRequest.builder() + * .bucket("my-bucket") + * .key("large-file") + * .overrideConfiguration(c -> c.addPlugin(highTimeoutPlugin)) + * .build(), + * ResponseTransformer.toBytes() + * ); + * } + * + * @param plugin the plugin to add + * @return this builder for method chaining + * @see SdkPlugin */ - @SdkPreviewApi B addPlugin(SdkPlugin plugin); /** * Returns the list of registered plugins */ - @SdkPreviewApi List plugins(); /** diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/SdkPlugin.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/SdkPlugin.java index 1048639926ae..8a4c611fba63 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/SdkPlugin.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/SdkPlugin.java @@ -15,23 +15,115 @@ package software.amazon.awssdk.core; -import software.amazon.awssdk.annotations.SdkPreviewApi; import software.amazon.awssdk.annotations.SdkPublicApi; import software.amazon.awssdk.annotations.ThreadSafe; import software.amazon.awssdk.utils.SdkAutoCloseable; /** - * A plugin modifies a client's configuration when the client is created or at request execution - * time. + * A plugin that modifies SDK client configuration at client creation time or per-request execution time. + * + *

Plugins provide an extensibility mechanism for customizing SDK client behavior without modifying core SDK code. + * They can modify configuration such as retry policies, timeouts, execution interceptors, endpoints, and authentication + * schemes. + * + *

Plugins can be applied at two levels: + *

    + *
  • Client-level: Applied once during client creation and affects all requests made by that client
  • + *
  • Request-level: Applied per-request and can override client-level configuration for specific requests
  • + *
+ * + *

When to use plugins vs direct configuration: + *

    + *
  • Use direct configuration for simple, one-time client setup specific to your application
  • + *
  • Use plugins when you need to: + *
      + *
    • Reuse the same configuration across multiple SDK clients
    • + *
    • Package configuration as a library or module for distribution
    • + *
    • Apply conditional or dynamic configuration logic
    • + *
    • Compose multiple configuration strategies together
    • + *
    + *
  • + *
+ * + *

Client-level plugin example: + * {@snippet : + * // Create a reusable plugin with multiple configuration settings + * SdkPlugin standardPlugin = config -> { + * config.endpointOverride(URI.create("https://127.0.0.1")) + * .overrideConfiguration(c -> c + * .apiCallTimeout(Duration.ofSeconds(30)) + * .addExecutionInterceptor(new LoggingInterceptor())); + * }; + * + * // Apply to multiple clients + * S3Client s3Client = S3Client.builder() + * .addPlugin(standardPlugin) + * .build(); + * + * DynamoDbClient dynamoClient = DynamoDbClient.builder() + * .addPlugin(standardPlugin) + * .build(); + * } + * + *

Composing multiple plugins: + * {@snippet : + * // Core plugin always applied + * SdkPlugin corePlugin = config -> { + * config.overrideConfiguration(c -> c.apiCallTimeout(Duration.ofSeconds(30))); + * }; + * + * // Optional feature plugins + * SdkPlugin compressionPlugin = config -> { + * config.overrideConfiguration(c -> c.compressionConfiguration( + * CompressionConfiguration.builder().requestCompressionEnabled(true).build())); + * }; + * + * SdkPlugin tracingPlugin = config -> { + * config.overrideConfiguration(c -> c.addExecutionInterceptor(new TracingInterceptor())); + * }; + * + * // Conditionally compose plugins based on feature flags + * S3Client.Builder builder = S3Client.builder().addPlugin(corePlugin); + * if (compressionEnabled) { + * builder.addPlugin(compressionPlugin); + * } + * if (tracingEnabled) { + * builder.addPlugin(tracingPlugin); + * } + * S3Client client = builder.build(); + * } + * + *

Plugins are invoked after default configuration is applied, allowing them to override SDK defaults. + * Multiple plugins can be registered and are executed in the order they were added. + * + *

Configuration precedence (highest to lowest): + *

    + *
  1. Plugin settings (applied last, highest precedence)
  2. + *
  3. Direct client builder settings (e.g., {@code .overrideConfiguration()})
  4. + *
  5. Service-specific defaults
  6. + *
  7. Global SDK defaults
  8. + *
+ * + *

Note: Request-level plugins have different precedence behavior. Request-level override configuration + * takes precedence over request-level plugin settings, meaning direct request configuration will override plugin + * settings for that request. + * + * @see software.amazon.awssdk.core.client.builder.SdkClientBuilder#addPlugin(SdkPlugin) + * @see software.amazon.awssdk.core.RequestOverrideConfiguration.Builder#addPlugin(SdkPlugin) */ -@SdkPreviewApi @SdkPublicApi @ThreadSafe @FunctionalInterface public interface SdkPlugin extends SdkAutoCloseable { /** - * Modify the provided client configuration. + * Modifies the provided client configuration. + * + *

This method is invoked by the SDK to allow the plugin to customize the client configuration. + * Implementations can modify any aspect of the configuration exposed through the builder, including + * override configuration, endpoints, and authentication schemes. + * + * @param config the configuration builder to modify */ void configureClient(SdkServiceClientConfiguration.Builder config); diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/builder/SdkClientBuilder.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/builder/SdkClientBuilder.java index b798724bde04..27a67896f6f2 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/builder/SdkClientBuilder.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/builder/SdkClientBuilder.java @@ -18,7 +18,6 @@ import java.net.URI; import java.util.List; import java.util.function.Consumer; -import software.amazon.awssdk.annotations.SdkPreviewApi; import software.amazon.awssdk.annotations.SdkPublicApi; import software.amazon.awssdk.core.SdkPlugin; import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; @@ -80,10 +79,31 @@ default B putAuthScheme(AuthScheme authScheme) { } /** - * Adds a plugin to the client builder. The plugins will be invoked when building the client to allow them to change the - * configuration of the built client. + * Adds a plugin to the client builder that will modify the client configuration when the client is built. + * + *

Plugins are invoked after all default configuration is applied, allowing them to override SDK defaults. + * Multiple plugins can be added and are executed in the order they were added. The configuration changes made + * by plugins apply to all requests made by the built client. + * + *

Important: Plugin settings have the highest precedence and will override any configuration set directly + * on the client builder (e.g., via {@code overrideConfiguration()}). + * + *

Example: + * {@snippet : + * SdkPlugin customPlugin = config -> { + * config.endpointOverride(URI.create("https://localhost:8080")) + * .overrideConfiguration(c -> c.apiCallTimeout(Duration.ofSeconds(30))); + * }; + * + * S3Client client = S3Client.builder() + * .addPlugin(customPlugin) + * .build(); + * } + * + * @param plugin the plugin to add + * @return this builder for method chaining + * @see SdkPlugin */ - @SdkPreviewApi default B addPlugin(SdkPlugin plugin) { throw new UnsupportedOperationException(); } @@ -91,7 +111,6 @@ default B addPlugin(SdkPlugin plugin) { /** * Returns the list of plugins configured on the client builder. */ - @SdkPreviewApi default List plugins() { throw new UnsupportedOperationException(); } diff --git a/services/ec2/src/main/java/software/amazon/awssdk/services/ec2/transform/internal/GeneratePreSignUrlInterceptor.java b/services/ec2/src/main/java/software/amazon/awssdk/services/ec2/transform/internal/GeneratePreSignUrlInterceptor.java index 7e8f314d0eca..10605a907c91 100644 --- a/services/ec2/src/main/java/software/amazon/awssdk/services/ec2/transform/internal/GeneratePreSignUrlInterceptor.java +++ b/services/ec2/src/main/java/software/amazon/awssdk/services/ec2/transform/internal/GeneratePreSignUrlInterceptor.java @@ -146,7 +146,6 @@ private Aws4PresignerParams getPresignerParams(ExecutionAttributes attributes, S .build(); } - // TODO(sra-identity-and-auth): add test case for SELECTED_AUTH_SCHEME case private AwsCredentials resolveCredentials(ExecutionAttributes attributes) { return attributes.getOptionalAttribute(SELECTED_AUTH_SCHEME) .map(selectedAuthScheme -> selectedAuthScheme.identity()) diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/AsyncRequestBodyFlexibleChecksumInTrailerTest.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/AsyncRequestBodyFlexibleChecksumInTrailerTest.java index 68eeb8d48bd8..80b2d18d8aa2 100644 --- a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/AsyncRequestBodyFlexibleChecksumInTrailerTest.java +++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/AsyncRequestBodyFlexibleChecksumInTrailerTest.java @@ -26,8 +26,6 @@ import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static com.github.tomakehurst.wiremock.client.WireMock.verify; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static software.amazon.awssdk.auth.signer.S3SignerExecutionAttribute.ENABLE_CHUNKED_ENCODING; -import static software.amazon.awssdk.auth.signer.S3SignerExecutionAttribute.ENABLE_PAYLOAD_SIGNING; import static software.amazon.awssdk.http.Header.CONTENT_LENGTH; import static software.amazon.awssdk.http.Header.CONTENT_TYPE; @@ -85,24 +83,12 @@ public void setupClient() { .credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create("akid", "skid"))) .region(Region.US_EAST_1) .endpointOverride(URI.create("http://localhost:" + wireMock.port())) - .overrideConfiguration( - // TODO(sra-identity-and-auth): we should remove these - // overrides once we set up codegen to set chunk-encoding to true - // for requests that are streaming and checksum-enabled - o -> o.putExecutionAttribute(ENABLE_CHUNKED_ENCODING, true) - .putExecutionAttribute(ENABLE_PAYLOAD_SIGNING, false)) .build(); asyncClient = ProtocolRestJsonAsyncClient.builder() .credentialsProvider(AnonymousCredentialsProvider.create()) .region(Region.US_EAST_1) .endpointOverride(URI.create("http://localhost:" + wireMock.port())) - .overrideConfiguration( - // TODO(sra-identity-and-auth): we should remove these - // overrides once we set up codegen to set chunk-encoding to true - // for requests that are streaming and checksum-enabled - o -> o.putExecutionAttribute(ENABLE_CHUNKED_ENCODING, true) - .putExecutionAttribute(ENABLE_PAYLOAD_SIGNING, false)) .build(); } diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/SdkPluginTest.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/SdkPluginTest.java index e6e257473bcd..f36061fe8583 100644 --- a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/SdkPluginTest.java +++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/SdkPluginTest.java @@ -38,7 +38,6 @@ import java.util.function.BiConsumer; import java.util.function.Supplier; import java.util.stream.Stream; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import org.mockito.Mockito; @@ -61,8 +60,6 @@ import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute; import software.amazon.awssdk.core.internal.retry.SdkDefaultRetryStrategy; -import software.amazon.awssdk.core.retry.RetryMode; -import software.amazon.awssdk.core.retry.RetryPolicy; import software.amazon.awssdk.endpoints.Endpoint; import software.amazon.awssdk.http.SdkHttpClient; import software.amazon.awssdk.http.auth.aws.scheme.AwsV4AuthScheme; @@ -77,7 +74,6 @@ import software.amazon.awssdk.profiles.ProfileFile; import software.amazon.awssdk.profiles.ProfileProperty; import software.amazon.awssdk.regions.Region; -import software.amazon.awssdk.retries.DefaultRetryStrategy; import software.amazon.awssdk.retries.api.RetryStrategy; import software.amazon.awssdk.services.protocolrestjson.ProtocolRestJsonClient; import software.amazon.awssdk.services.protocolrestjson.ProtocolRestJsonClientBuilder; @@ -467,7 +463,6 @@ public void requestPluginSeesCustomerClientConfiguredValue(TestCase testC @ParameterizedTest @MethodSource("testCases") - @Disabled("Request-level values are currently higher-priority than plugin settings.") // TODO(sra-identity-auth) public void requestPluginSeesCustomerRequestConfiguredValue(TestCase testCase) { if (testCase.requestSetter == null) { System.out.println("No request setting available."); @@ -480,7 +475,11 @@ public void requestPluginSeesCustomerRequestConfiguredValue(TestCase test SdkPlugin plugin = config -> { ProtocolRestJsonServiceClientConfiguration.Builder conf = (ProtocolRestJsonServiceClientConfiguration.Builder) config; - testCase.pluginValidator.accept(conf, testCase.nonDefaultValue); + // Request-level values are currently higher-priority than plugin settings. + // This is not the intended behavior and is inconsistent with client-level plugin. + // However, we can't fix it directly due to backwards compatibility. See + // JAVA-8671 for more details. + testCase.pluginValidator.accept(conf, testCase.defaultValue); timesCalled.incrementAndGet(); }; @@ -570,8 +569,11 @@ public void beforeTransmission(Context.BeforeTransmission context, ExecutionAttr AwsRequestOverrideConfiguration.builder() .addPlugin(plugin) .applyMutation(c -> { - // TODO(sra-identity-auth): request-level plugins should override request-level - // configuration + // testCase.requestSetter is not applied here because request-level values are + // currently higher-priority than plugin settings. + // This is not the intended behavior and is inconsistent with client-level plugin. + // However, we can't fix it directly due to backwards compatibility. See + // JAVA-8671 for more details. // if (testCase.requestSetter != null) { // testCase.requestSetter.accept(c, testCase.defaultValue); // }