diff --git a/docs/src/main/asciidoc/cloudwatch.adoc b/docs/src/main/asciidoc/cloudwatch.adoc index 59b2516b6..2da1ce4f8 100644 --- a/docs/src/main/asciidoc/cloudwatch.adoc +++ b/docs/src/main/asciidoc/cloudwatch.adoc @@ -40,3 +40,30 @@ Following configuration properties are available to configure CloudWatch integra | |The specific region for CloudWatch integration. |=== + +=== Client Customization + +`CloudWatchAsyncClient` can be further customized by providing a bean of type `CloudWatchAsyncClientCustomizer`: + +[source,java] +---- +@Bean +CloudWatchAsyncClientCustomizer customizer() { + return builder -> { + builder.overrideConfiguration(builder.overrideConfiguration().copy(c -> { + c.apiCallTimeout(Duration.ofMillis(1500)); + })); + }; +} +---- + +[WARNING] +==== +`builder.overrideConfiguration(..)` replaces the configuration object, so always make sure to use `builder.overrideConfiguration().copy(c -> ..)` to configure only certain properties and keep the already pre-configured values for others. +==== + +`CloudWatchAsyncClientCustomizer` is a functional interface that enables configuring `CloudWatchAsyncClientBuilder` before the `CloudWatchAsyncClient` is built in auto-configuration. + +There can be multiple `CloudWatchAsyncClientCustomizer` beans present in single application context. `@Order(..)` annotation can be used to define the order of the execution. + +Note that `CloudWatchAsyncClientCustomizer` beans are applied **after** `AwsAsyncClientCustomizer` beans and therefore can overwrite previously set configurations. diff --git a/docs/src/main/asciidoc/core.adoc b/docs/src/main/asciidoc/core.adoc index b7dbb85fd..17261394f 100644 --- a/docs/src/main/asciidoc/core.adoc +++ b/docs/src/main/asciidoc/core.adoc @@ -214,38 +214,49 @@ To simplify using services with AWS compatible APIs, or running applications aga === Customizing AWS Clients -To configure an AWS client with custom HTTP client or `ClientOverrideConfiguration`, define a bean of type `AwsClientConfigurer` with a type parameter indicating configured client builder. +Properties cover the most common configuration needs. When more advanced configuration is required, Spring Cloud AWS offers a set of customizer interfaces that can be implemented to customize AWS clients. -[source,java,indent=0] ----- -import io.awspring.cloud.autoconfigure.core.AwsClientCustomizer; -import org.springframework.context.annotation.Bean; +There are two types of AWS clients - synchronous and asynchronous. Each Spring Cloud AWS integration use one or the other type: -import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; -import software.amazon.awssdk.http.SdkHttpClient; -import software.amazon.awssdk.http.apache.ApacheHttpClient; -import software.amazon.awssdk.services.sns.SnsClientBuilder; +[cols="2*", options="header"] +|=== +|client type +|integrations -import java.time.Duration; +|synchronous +|DynamoDB, SES, SNS, Parameter Store, Secrets Manager, S3 -@Configuration -class S3AwsClientConfigurerConfiguration { +|asynchronous +|SQS, CloudWatch +|=== - @Bean - AwsClientCustomizer s3ClientBuilderAwsClientConfigurer() { - return new S3AwsClientClientConfigurer(); - } +To customize every synchronous client, provide a bean of type `AwsSyncClientCustomizer`. For example: + +[source,java,indent=0] +---- +import io.awspring.cloud.autoconfigure.AwsSyncClientCustomizer; - static class S3AwsClientClientConfigurer implements AwsClientCustomizer { - @Override - public ClientOverrideConfiguration overrideConfiguration() { - return ClientOverrideConfiguration.builder().apiCallTimeout(Duration.ofMillis(500)).build(); - } +@Bean +AwsSyncClientCustomizer awsSyncClientCustomizer() { + return builder -> { + builder.httpClient(ApacheHttpClient.builder().connectionTimeout(Duration.ofSeconds(1)).build()); + }; +} +---- - @Override - public SdkHttpClient httpClient() { - return ApacheHttpClient.builder().connectionTimeout(Duration.ofMillis(1000)).build(); - } - } +To customize every asynchronous client, provide a bean of type `AwsAsyncClientCustomizer`. For example: + +[source,java,indent=0] +---- +@Bean +AwsAsyncClientCustomizer awsAsyncClientCustomizer() { + return builder -> { + builder.httpClient(NettyNioAsyncHttpClient.builder().connectionTimeout(Duration.ofSeconds(1)).build()); + }; } ---- + +There can be multiple customizer beans present in single application context and all of them will be used to configure AWS clients. If order of customizer matters, use `@Order` annotation on customizer beans. + +Client-specific customizations can be applied through client-specific customizer interfaces (for example `S3ClientCustomizer` for S3). See integrations documentation for details. + diff --git a/docs/src/main/asciidoc/dynamodb.adoc b/docs/src/main/asciidoc/dynamodb.adoc index 557a242fb..619dff3e8 100644 --- a/docs/src/main/asciidoc/dynamodb.adoc +++ b/docs/src/main/asciidoc/dynamodb.adoc @@ -150,6 +150,33 @@ The Spring Boot Starter for DynamoDb provides the following configuration option | `spring.cloud.aws.dynamodb.dax.skip-host-name-verification` | Skips hostname verification in url. | No | |=== +=== Client Customization + +`DynamoDbClient` can be further customized by providing a bean of type `DynamoDbClientCustomizer`: + +[source,java] +---- +@Bean +DynamoDbClientCustomizer customizer() { + return builder -> { + builder.overrideConfiguration(builder.overrideConfiguration().copy(c -> { + c.apiCallTimeout(Duration.ofMillis(1500)); + })); + }; +} +---- + +[WARNING] +==== +`builder.overrideConfiguration(..)` replaces the configuration object, so always make sure to use `builder.overrideConfiguration().copy(c -> ..)` to configure only certain properties and keep the already pre-configured values for others. +==== + +`DynamoDbClientCustomizer` is a functional interface that enables configuring `DynamoDbClientBuilder` before the `DynamoDbClient` is built in auto-configuration. + +There can be multiple `DynamoDbClientCustomizer` beans present in single application context. `@Order(..)` annotation can be used to define the order of the execution. + +Note that `DynamoDbClientCustomizer` beans are applied **after** `AwsSyncClientCustomizer` beans and therefore can overwrite previously set configurations. + === IAM Permissions Since it depends on how you will use DynamoDb integration providing a list of IAM policies would be pointless since least privilege model should be used. diff --git a/docs/src/main/asciidoc/parameter-store.adoc b/docs/src/main/asciidoc/parameter-store.adoc index f63c3e4b7..ea13bc513 100644 --- a/docs/src/main/asciidoc/parameter-store.adoc +++ b/docs/src/main/asciidoc/parameter-store.adoc @@ -94,7 +94,7 @@ The starter automatically configures and registers a `SsmClient` bean in the Spr [source,java] ---- -import org.springframework.stereotype.Component; +import org.springframework.beans.factory.annotation.Autowired; import software.amazon.awssdk.services.ssm.SsmClient; ... @Autowired @@ -139,14 +139,14 @@ Note that this class must be listed under `org.springframework.boot.BootstrapReg org.springframework.boot.BootstrapRegistryInitializer=com.app.ParameterStoreBootstrapConfiguration ---- -If you want to use autoconfigured `SsmClient` but change underlying SDKClient or ClientOverrideConfiguration you will need to register bean of type `AwsClientConfigurerParameterStore`: +If you want to use autoconfigured `SsmClient` but change underlying `SDKClient` or `ClientOverrideConfiguration` you will need to register bean of type `SsmClientCustomizer`: Autoconfiguration will configure `SsmClient` Bean with provided values after that, for example: [source,java] ---- package com.app; -import io.awspring.cloud.autoconfigure.config.parameterstore.AwsParameterStoreClientCustomizer; +import io.awspring.cloud.autoconfigure.config.parameterstore.SsmClientCustomizer; import java.time.Duration; import org.springframework.boot.BootstrapRegistry; import org.springframework.boot.BootstrapRegistryInitializer; @@ -159,20 +159,11 @@ class ParameterStoreBootstrapConfiguration implements BootstrapRegistryInitializ @Override public void initialize(BootstrapRegistry registry) { - registry.register(AwsParameterStoreClientCustomizer.class, - context -> new AwsParameterStoreClientCustomizer() { - - @Override - public ClientOverrideConfiguration overrideConfiguration() { - return ClientOverrideConfiguration.builder().apiCallTimeout(Duration.ofMillis(500)) - .build(); - } - - @Override - public SdkHttpClient httpClient() { - return ApacheHttpClient.builder().connectionTimeout(Duration.ofMillis(1000)).build(); - } - }); + registry.register(SsmClientCustomizer.class, context -> (builder -> { + builder.overrideConfiguration(builder.overrideConfiguration().copy(c -> { + c.apiCallTimeout(Duration.ofMillis(2000)); + })); + })); } } ---- diff --git a/docs/src/main/asciidoc/s3.adoc b/docs/src/main/asciidoc/s3.adoc index 8a7df66a7..e5c3af98c 100644 --- a/docs/src/main/asciidoc/s3.adoc +++ b/docs/src/main/asciidoc/s3.adoc @@ -254,6 +254,33 @@ The Spring Boot Starter for S3 provides the following configuration options: | `spring.cloud.aws.s3.transfer-manager.follow-symbolic-links` | Specifies whether to follow symbolic links when traversing the file tree in `S3TransferManager#uploadDirectory` operation | No | `null` (falls back to SDK default) |=== +=== Client Customization + +`S3Client` can be further customized by providing a bean of type `S3ClientCustomizer`: + +[source,java] +---- +@Bean +S3ClientCustomizer customizer() { + return builder -> { + builder.overrideConfiguration(builder.overrideConfiguration().copy(c -> { + c.apiCallTimeout(Duration.ofMillis(1500)); + })); + }; +} +---- + +[WARNING] +==== +`builder.overrideConfiguration(..)` replaces the configuration object, so always make sure to use `builder.overrideConfiguration().copy(c -> ..)` to configure only certain properties and keep the already pre-configured values for others. +==== + +`S3ClientCustomizer` is a functional interface that enables configuring `S3ClientBuilder` before the `S3Client` is built in auto-configuration. + +There can be multiple `S3ClientCustomizer` beans present in single application context. `@Order(..)` annotation can be used to define the order of the execution. + +Note that `S3ClientCustomizer` beans are applied **after** `AwsSyncClientCustomizer` beans and therefore can overwrite previously set configurations. + === IAM Permissions Following IAM permissions are required by Spring Cloud AWS: diff --git a/docs/src/main/asciidoc/secrets-manager.adoc b/docs/src/main/asciidoc/secrets-manager.adoc index 8537ff2f1..d7c6cc0eb 100644 --- a/docs/src/main/asciidoc/secrets-manager.adoc +++ b/docs/src/main/asciidoc/secrets-manager.adoc @@ -145,7 +145,7 @@ The starter automatically configures and registers a `SecretsManagerClient` bean [source,java] ---- -import org.springframework.stereotype.Component; +import org.springframework.beans.factory.annotation.Autowired; import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient; import software.amazon.awssdk.services.secretsmanager.model.CreateSecretRequest; ... @@ -191,14 +191,14 @@ Note that this class must be listed under `org.springframework.boot.BootstrapReg org.springframework.boot.BootstrapRegistryInitializer=com.app.SecretsManagerBootstrapConfiguration ---- -If you want to use autoconfigured `SecretsManagerClient` but change underlying SDKClient or ClientOverrideConfiguration you will need to register bean of type `AwsClientConfigurerSecretsManager`: +If you want to use autoconfigured `SecretsManagerClient` but change underlying SDKClient or `ClientOverrideConfiguration` you will need to register bean of type `SecretsManagerClientCustomizer`: Autoconfiguration will configure `SecretsManagerClient` Bean with provided values after that, for example: [source,java] ---- package com.app; -import io.awspring.cloud.autoconfigure.config.secretsmanager.AwsSecretsManagerClientCustomizer; +import io.awspring.cloud.autoconfigure.config.secretsmanager.SecretsManagerClientCustomizer; import java.time.Duration; import org.springframework.boot.BootstrapRegistry; import org.springframework.boot.BootstrapRegistryInitializer; @@ -211,20 +211,11 @@ class SecretsManagerBootstrapConfiguration implements BootstrapRegistryInitializ @Override public void initialize(BootstrapRegistry registry) { - registry.register(AwsSecretsManagerClientCustomizer.class, - context -> new AwsSecretsManagerClientCustomizer() { - - @Override - public ClientOverrideConfiguration overrideConfiguration() { - return ClientOverrideConfiguration.builder().apiCallTimeout(Duration.ofMillis(500)) - .build(); - } - - @Override - public SdkHttpClient httpClient() { - return ApacheHttpClient.builder().connectionTimeout(Duration.ofMillis(1000)).build(); - } - }); + registry.register(SecretsManagerClientCustomizer.class, context -> (builder -> { + builder.overrideConfiguration(builder.overrideConfiguration().copy(c -> { + c.apiCallTimeout(Duration.ofMillis(2001)); + })); + })); } } ---- diff --git a/docs/src/main/asciidoc/ses.adoc b/docs/src/main/asciidoc/ses.adoc index 213081542..404f325e7 100644 --- a/docs/src/main/asciidoc/ses.adoc +++ b/docs/src/main/asciidoc/ses.adoc @@ -163,6 +163,33 @@ spring.cloud.aws.ses.from-arn=arn:aws:ses:eu-west-1:123456789012:identity/exampl spring.cloud.aws.ses.configuration-set-name=ConfigSet ---- +=== Client Customization + +`SesClient` can be further customized by providing a bean of type `SesClientCustomizer`: + +[source,java] +---- +@Bean +SesClientCustomizer customizer() { + return builder -> { + builder.overrideConfiguration(builder.overrideConfiguration().copy(c -> { + c.apiCallTimeout(Duration.ofMillis(1500)); + })); + }; +} +---- + +[WARNING] +==== +`builder.overrideConfiguration(..)` replaces the configuration object, so always make sure to use `builder.overrideConfiguration().copy(c -> ..)` to configure only certain properties and keep the already pre-configured values for others. +==== + +`SesClientCustomizer` is a functional interface that enables configuring `SesClientBuilder` before the `SesClient` is built in auto-configuration. + +There can be multiple `SesClientCustomizer` beans present in single application context. `@Order(..)` annotation can be used to define the order of the execution. + +Note that `SesClientCustomizer` beans are applied **after** `AwsSyncClientCustomizer` beans and therefore can overwrite previously set configurations. + === IAM Permissions Following IAM permissions are required by Spring Cloud AWS: diff --git a/docs/src/main/asciidoc/sns.adoc b/docs/src/main/asciidoc/sns.adoc index 8d098f837..647ba3731 100644 --- a/docs/src/main/asciidoc/sns.adoc +++ b/docs/src/main/asciidoc/sns.adoc @@ -228,6 +228,33 @@ The Spring Boot Starter for SNS provides the following configuration options: | `spring.cloud.aws.sns.region` | Configures region used by `SnsClient`. | No | `eu-west-1` |=== +=== Client Customization + +`SnsClient` can be further customized by providing a bean of type `SnsClientCustomizer`: + +[source,java] +---- +@Bean +SnsClientCustomizer customizer() { + return builder -> { + builder.overrideConfiguration(builder.overrideConfiguration().copy(c -> { + c.apiCallTimeout(Duration.ofMillis(1500)); + })); + }; +} +---- + +[WARNING] +==== +`builder.overrideConfiguration(..)` replaces the configuration object, so always make sure to use `builder.overrideConfiguration().copy(c -> ..)` to configure only certain properties and keep the already pre-configured values for others. +==== + +`SnsClientCustomizer` is a functional interface that enables configuring `SnsClientBuilder` before the `SnsClient` is built in auto-configuration. + +There can be multiple `SnsClientCustomizer` beans present in single application context. `@Order(..)` annotation can be used to define the order of the execution. + +Note that `SnsClientCustomizer` beans are applied **after** `AwsSyncClientCustomizer` beans and therefore can overwrite previously set configurations. + === IAM Permissions Following IAM permissions are required by Spring Cloud AWS: @@ -237,7 +264,7 @@ Following IAM permissions are required by Spring Cloud AWS: | To publish notification you will also need | `sns:ListTopics` | To use Annotation-driven HTTP notification endpoint | `sns:ConfirmSubscription` | For resolving topic name to ARN | `sns:CreateTopic` -| For validating topic existence by ARN | `sns:GetTopicAttributes` +| For validating topic existence by ARN | `sns:GetTopicAttributes` |=== Sample IAM policy granting access to SNS: diff --git a/docs/src/main/asciidoc/sqs.adoc b/docs/src/main/asciidoc/sqs.adoc index bd60f4194..8acffc7ea 100644 --- a/docs/src/main/asciidoc/sqs.adoc +++ b/docs/src/main/asciidoc/sqs.adoc @@ -1682,6 +1682,34 @@ When providing a custom executor, it's important that it's configured to support IMPORTANT: To avoid unnecessary thread hopping between blocking components, a `MessageExecutionThreadFactory` MUST be set to the executor. +=== Client Customization + +`SqsAsyncClient` can be further customized by providing a bean of type `SqsAsyncClientCustomizer`: + +[source,java] +---- +@Bean +SqsAsyncClientCustomizer customizer() { + return builder -> { + builder.overrideConfiguration(builder.overrideConfiguration().copy(c -> { + c.apiCallTimeout(Duration.ofMillis(1500)); + })); + }; +} +---- + +[WARNING] +==== +`builder.overrideConfiguration(..)` replaces the configuration object, so always make sure to use `builder.overrideConfiguration().copy(c -> ..)` to configure only certain properties and keep the already pre-configured values for others. +==== + +`SqsAsyncClientCustomizer` is a functional interface that enables configuring `SqsAsyncClientBuilder` before the `SqsAsyncClient` is built in auto-configuration. + +There can be multiple `SqsAsyncClientCustomizer` beans present in single application context. `@Order(..)` annotation can be used to define the order of the execution. + +Note that `SqsAsyncClientCustomizer` beans are applied **after** `AwsAsyncClientCustomizer` beans and therefore can overwrite previously set configurations. + + === IAM Permissions Following IAM permissions are required by Spring Cloud AWS SQS: diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/AwsAsyncClientCustomizer.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/AwsAsyncClientCustomizer.java new file mode 100644 index 000000000..99f63daff --- /dev/null +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/AwsAsyncClientCustomizer.java @@ -0,0 +1,35 @@ +/* + * Copyright 2013-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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 io.awspring.cloud.autoconfigure; + +import software.amazon.awssdk.awscore.client.builder.AwsAsyncClientBuilder; + +/** + * Callback interface that can be used to customize a {@link AwsAsyncClientBuilder}. + *

+ * It gets applied to every configured asynchronous AWS client bean. + * + * @author Maciej Walkowiak + * @since 3.3.0 + */ +public interface AwsAsyncClientCustomizer { + /** + * Callback to customize a {@link AwsAsyncClientBuilder} instance. + * + * @param builder the client builder to customize + */ + void customize(AwsAsyncClientBuilder builder); +} diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/AwsClientCustomizer.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/AwsClientCustomizer.java new file mode 100644 index 000000000..d077d4229 --- /dev/null +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/AwsClientCustomizer.java @@ -0,0 +1,35 @@ +/* + * Copyright 2013-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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 io.awspring.cloud.autoconfigure; + +import software.amazon.awssdk.awscore.client.builder.AwsClientBuilder; + +/** + * Base callback interface to be extended by customizers for specific AWS clients. + * + * @param - type of AWS client builder to customize + * @author Maciej Walkowiak + * @since 3.3.0 + */ +public interface AwsClientCustomizer> { + + /** + * Callback to customize an instance of AWS client builder. + * + * @param builder the client builder to customize + */ + void customize(T builder); +} diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/AwsSyncClientCustomizer.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/AwsSyncClientCustomizer.java new file mode 100644 index 000000000..7235d2742 --- /dev/null +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/AwsSyncClientCustomizer.java @@ -0,0 +1,35 @@ +/* + * Copyright 2013-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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 io.awspring.cloud.autoconfigure; + +import software.amazon.awssdk.awscore.client.builder.AwsSyncClientBuilder; + +/** + * Callback interface that can be used to customize a {@link AwsSyncClientBuilder}. + *

+ * It gets applied to every configured synchronous AWS client bean. + * + * @author Maciej Walkowiak + * @since 3.3.0 + */ +public interface AwsSyncClientCustomizer { + /** + * Callback to customize a {@link AwsSyncClientBuilder} instance. + * + * @param builder the client builder to customize + */ + void customize(AwsSyncClientBuilder builder); +} diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/parameterstore/AwsParameterStoreClientCustomizer.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/parameterstore/AwsParameterStoreClientCustomizer.java index 728759f90..aaf454e52 100644 --- a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/parameterstore/AwsParameterStoreClientCustomizer.java +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/parameterstore/AwsParameterStoreClientCustomizer.java @@ -19,8 +19,10 @@ import software.amazon.awssdk.services.ssm.SsmClientBuilder; /** + * @deprecated use {@link SsmClientCustomizer} * @author Matej Nedic * @since 3.0.0 */ +@Deprecated(since = "3.3.0", forRemoval = true) public interface AwsParameterStoreClientCustomizer extends AwsClientCustomizer { } diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/parameterstore/ParameterStoreAutoConfiguration.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/parameterstore/ParameterStoreAutoConfiguration.java index f397a7387..02c044489 100644 --- a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/parameterstore/ParameterStoreAutoConfiguration.java +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/parameterstore/ParameterStoreAutoConfiguration.java @@ -15,6 +15,7 @@ */ package io.awspring.cloud.autoconfigure.config.parameterstore; +import io.awspring.cloud.autoconfigure.AwsSyncClientCustomizer; import io.awspring.cloud.autoconfigure.core.AwsAutoConfiguration; import io.awspring.cloud.autoconfigure.core.AwsClientBuilderConfigurer; import io.awspring.cloud.autoconfigure.core.AwsClientCustomizer; @@ -51,9 +52,12 @@ public class ParameterStoreAutoConfiguration { public SsmClient ssmClient(ParameterStoreProperties properties, AwsClientBuilderConfigurer awsClientBuilderConfigurer, ObjectProvider> customizers, + ObjectProvider ssmClientCustomizers, + ObjectProvider awsSyncClientCustomizers, ObjectProvider connectionDetails) { - return awsClientBuilderConfigurer.configure(SsmClient.builder(), properties, connectionDetails.getIfAvailable(), - customizers.getIfAvailable()).build(); + return awsClientBuilderConfigurer.configureSyncClient(SsmClient.builder(), properties, + connectionDetails.getIfAvailable(), customizers.getIfAvailable(), ssmClientCustomizers.orderedStream(), + awsSyncClientCustomizers.orderedStream()).build(); } } diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/parameterstore/ParameterStoreConfigDataLocationResolver.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/parameterstore/ParameterStoreConfigDataLocationResolver.java index 68c51ba58..78b3bd3fd 100644 --- a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/parameterstore/ParameterStoreConfigDataLocationResolver.java +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/parameterstore/ParameterStoreConfigDataLocationResolver.java @@ -15,6 +15,7 @@ */ package io.awspring.cloud.autoconfigure.config.parameterstore; +import io.awspring.cloud.autoconfigure.AwsSyncClientCustomizer; import io.awspring.cloud.autoconfigure.config.AbstractAwsConfigDataLocationResolver; import io.awspring.cloud.autoconfigure.core.AwsClientCustomizer; import io.awspring.cloud.autoconfigure.core.AwsProperties; @@ -102,8 +103,29 @@ protected SsmClient createSimpleSystemManagementClient(BootstrapContext context) } } catch (IllegalStateException e) { - log.debug("Bean of type AwsClientConfigurerParameterStore is not registered: " + e.getMessage()); + log.debug("Bean of type AwsParameterStoreClientCustomizer is not registered: " + e.getMessage()); } + + try { + AwsSyncClientCustomizer awsSyncClientCustomizer = context.get(AwsSyncClientCustomizer.class); + if (awsSyncClientCustomizer != null) { + awsSyncClientCustomizer.customize(builder); + } + } + catch (IllegalStateException e) { + log.debug("Bean of type AwsSyncClientCustomizer is not registered: " + e.getMessage()); + } + + try { + SsmClientCustomizer ssmClientCustomizer = context.get(SsmClientCustomizer.class); + if (ssmClientCustomizer != null) { + ssmClientCustomizer.customize(builder); + } + } + catch (IllegalStateException e) { + log.debug("Bean of type SsmClientCustomizer is not registered: " + e.getMessage()); + } + return builder.build(); } diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/parameterstore/SsmClientCustomizer.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/parameterstore/SsmClientCustomizer.java new file mode 100644 index 000000000..062448571 --- /dev/null +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/parameterstore/SsmClientCustomizer.java @@ -0,0 +1,29 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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 io.awspring.cloud.autoconfigure.config.parameterstore; + +import io.awspring.cloud.autoconfigure.AwsClientCustomizer; +import software.amazon.awssdk.services.ssm.SsmClientBuilder; + +/** + * Callback interface that can be used to customize a {@link SsmClientBuilder}. + * + * @author Maciej Walkowiak + * @since 3.3.0 + */ +@FunctionalInterface +public interface SsmClientCustomizer extends AwsClientCustomizer { +} diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/secretsmanager/AwsSecretsManagerClientCustomizer.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/secretsmanager/AwsSecretsManagerClientCustomizer.java index 0991f8605..01a5a5464 100644 --- a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/secretsmanager/AwsSecretsManagerClientCustomizer.java +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/secretsmanager/AwsSecretsManagerClientCustomizer.java @@ -19,8 +19,10 @@ import software.amazon.awssdk.services.secretsmanager.SecretsManagerClientBuilder; /** + * @deprecated use {@link SecretsManagerClientCustomizer} * @author Matej Nedic * @since 3.0.0 */ +@Deprecated(since = "3.3.0", forRemoval = true) public interface AwsSecretsManagerClientCustomizer extends AwsClientCustomizer { } diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/secretsmanager/SecretsManagerAutoConfiguration.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/secretsmanager/SecretsManagerAutoConfiguration.java index 9edaabad5..f79518234 100644 --- a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/secretsmanager/SecretsManagerAutoConfiguration.java +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/secretsmanager/SecretsManagerAutoConfiguration.java @@ -15,6 +15,7 @@ */ package io.awspring.cloud.autoconfigure.config.secretsmanager; +import io.awspring.cloud.autoconfigure.AwsSyncClientCustomizer; import io.awspring.cloud.autoconfigure.core.AwsAutoConfiguration; import io.awspring.cloud.autoconfigure.core.AwsClientBuilderConfigurer; import io.awspring.cloud.autoconfigure.core.AwsClientCustomizer; @@ -48,8 +49,11 @@ public class SecretsManagerAutoConfiguration { public SecretsManagerClient secretsManagerClient(SecretsManagerProperties properties, AwsClientBuilderConfigurer awsClientBuilderConfigurer, ObjectProvider> customizer, - ObjectProvider connectionDetails) { - return awsClientBuilderConfigurer.configure(SecretsManagerClient.builder(), properties, - connectionDetails.getIfAvailable(), customizer.getIfAvailable()).build(); + ObjectProvider connectionDetails, + ObjectProvider secretsManagerClientCustomizers, + ObjectProvider awsSyncClientCustomizers) { + return awsClientBuilderConfigurer.configureSyncClient(SecretsManagerClient.builder(), properties, + connectionDetails.getIfAvailable(), customizer.getIfAvailable(), + secretsManagerClientCustomizers.orderedStream(), awsSyncClientCustomizers.orderedStream()).build(); } } diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/secretsmanager/SecretsManagerClientCustomizer.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/secretsmanager/SecretsManagerClientCustomizer.java new file mode 100644 index 000000000..789e98f15 --- /dev/null +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/secretsmanager/SecretsManagerClientCustomizer.java @@ -0,0 +1,29 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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 io.awspring.cloud.autoconfigure.config.secretsmanager; + +import io.awspring.cloud.autoconfigure.AwsClientCustomizer; +import software.amazon.awssdk.services.secretsmanager.SecretsManagerClientBuilder; + +/** + * Callback interface that can be used to customize a {@link SecretsManagerClientBuilder}. + * + * @author Maciej Walkowiak + * @since 3.3.0 + */ +@FunctionalInterface +public interface SecretsManagerClientCustomizer extends AwsClientCustomizer { +} diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/secretsmanager/SecretsManagerConfigDataLocationResolver.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/secretsmanager/SecretsManagerConfigDataLocationResolver.java index 330308c71..3005d74d3 100644 --- a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/secretsmanager/SecretsManagerConfigDataLocationResolver.java +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/config/secretsmanager/SecretsManagerConfigDataLocationResolver.java @@ -15,6 +15,7 @@ */ package io.awspring.cloud.autoconfigure.config.secretsmanager; +import io.awspring.cloud.autoconfigure.AwsSyncClientCustomizer; import io.awspring.cloud.autoconfigure.config.AbstractAwsConfigDataLocationResolver; import io.awspring.cloud.autoconfigure.core.AwsClientCustomizer; import io.awspring.cloud.autoconfigure.core.AwsProperties; @@ -106,8 +107,30 @@ protected SecretsManagerClient createAwsSecretsManagerClient(BootstrapContext co } } catch (IllegalStateException e) { - log.debug("Bean of type AwsClientConfigurerSecretsManager is not registered: " + e.getMessage()); + log.debug("Bean of type AwsParameterStoreClientCustomizer is not registered: " + e.getMessage()); } + + try { + AwsSyncClientCustomizer awsSyncClientCustomizer = context.get(AwsSyncClientCustomizer.class); + if (awsSyncClientCustomizer != null) { + awsSyncClientCustomizer.customize(builder); + } + } + catch (IllegalStateException e) { + log.debug("Bean of type AwsSyncClientCustomizer is not registered: " + e.getMessage()); + } + + try { + SecretsManagerClientCustomizer secretsManagerClientCustomizer = context + .get(SecretsManagerClientCustomizer.class); + if (secretsManagerClientCustomizer != null) { + secretsManagerClientCustomizer.customize(builder); + } + } + catch (IllegalStateException e) { + log.debug("Bean of type SecretsManagerClientCustomizer is not registered: " + e.getMessage()); + } + return builder.build(); } diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/core/AwsClientBuilderConfigurer.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/core/AwsClientBuilderConfigurer.java index 8364a2c3e..20c47c9c3 100644 --- a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/core/AwsClientBuilderConfigurer.java +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/core/AwsClientBuilderConfigurer.java @@ -15,14 +15,20 @@ */ package io.awspring.cloud.autoconfigure.core; +import io.awspring.cloud.autoconfigure.AwsAsyncClientCustomizer; +import io.awspring.cloud.autoconfigure.AwsClientCustomizer; import io.awspring.cloud.autoconfigure.AwsClientProperties; +import io.awspring.cloud.autoconfigure.AwsSyncClientCustomizer; import io.awspring.cloud.core.SpringCloudClientConfiguration; import java.util.Optional; +import java.util.stream.Stream; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; +import software.amazon.awssdk.awscore.client.builder.AwsAsyncClientBuilder; import software.amazon.awssdk.awscore.client.builder.AwsClientBuilder; +import software.amazon.awssdk.awscore.client.builder.AwsSyncClientBuilder; import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.regions.providers.AwsRegionProvider; @@ -47,17 +53,34 @@ public AwsClientBuilderConfigurer(AwsCredentialsProvider credentialsProvider, Aw this.clientOverrideConfiguration = new SpringCloudClientConfiguration().clientOverrideConfiguration(); } - public > T configure(T builder) { + public > T configure(T builder) { return configure(builder, null, null, null); } - public > T configure(T builder, @Nullable AwsClientProperties clientProperties, - @Nullable AwsClientCustomizer customizer) { + /** + * @deprecated use + * {@link #configureSyncClient(AwsClientBuilder, AwsClientProperties, AwsConnectionDetails, Stream, Stream)} for + * sync client or + * {@link #configureAsyncClient(AwsClientBuilder, AwsClientProperties, AwsConnectionDetails, Stream, Stream)} for + * async client. + */ + @Deprecated + public > T configure(T builder, @Nullable AwsClientProperties clientProperties, + @Nullable io.awspring.cloud.autoconfigure.core.AwsClientCustomizer customizer) { return configure(builder, clientProperties, null, customizer); } - public > T configure(T builder, @Nullable AwsClientProperties clientProperties, - @Nullable AwsConnectionDetails connectionDetails, @Nullable AwsClientCustomizer customizer) { + /** + * @deprecated use + * {@link #configureSyncClient(AwsClientBuilder, AwsClientProperties, AwsConnectionDetails, Stream, Stream)} for + * sync client or + * {@link #configureAsyncClient(AwsClientBuilder, AwsClientProperties, AwsConnectionDetails, Stream, Stream)} for + * async client. + */ + @Deprecated + public > T configure(T builder, @Nullable AwsClientProperties clientProperties, + @Nullable AwsConnectionDetails connectionDetails, + @Nullable io.awspring.cloud.autoconfigure.core.AwsClientCustomizer customizer) { Assert.notNull(builder, "builder is required"); builder.credentialsProvider(this.credentialsProvider).region(resolveRegion(clientProperties, connectionDetails)) @@ -72,11 +95,71 @@ public AwsClientBuilderConfigurer(AwsCredentialsProvider credentialsProvider, Aw Optional.ofNullable(this.awsProperties.getFipsEnabled()).ifPresent(builder::fipsEnabled); Optional.ofNullable(this.awsProperties.getDualstackEnabled()).ifPresent(builder::dualstackEnabled); if (customizer != null) { - AwsClientCustomizer.apply(customizer, builder); + io.awspring.cloud.autoconfigure.core.AwsClientCustomizer.apply(customizer, builder); } return builder; } + public > T configureSyncClient(T builder, + @Nullable AwsClientProperties clientProperties, @Nullable AwsConnectionDetails connectionDetails, + @Nullable Stream> clientBuilderCustomizer, + @Nullable Stream commonCustomizers) { + return configureSyncClient(builder, clientProperties, connectionDetails, null, clientBuilderCustomizer, + commonCustomizers); + } + + public > T configureAsyncClient(T builder, + @Nullable AwsClientProperties clientProperties, @Nullable AwsConnectionDetails connectionDetails, + @Nullable Stream> clientBuilderCustomizer, + @Nullable Stream commonCustomizers) { + return configureAsyncClient(builder, clientProperties, connectionDetails, null, clientBuilderCustomizer, + commonCustomizers); + } + + @Deprecated + public > T configure(T builder, @Nullable AwsClientProperties clientProperties, + @Nullable AwsConnectionDetails connectionDetails, + @Nullable io.awspring.cloud.autoconfigure.core.AwsClientCustomizer customizer, + @Nullable Stream> clientBuilderCustomizer) { + return configure(builder, clientProperties, connectionDetails, null, clientBuilderCustomizer); + } + + /** + * @deprecated use + * {@link #configureSyncClient(AwsClientBuilder, AwsClientProperties, AwsConnectionDetails, Stream, Stream)}. + */ + @Deprecated + public > T configureSyncClient(T builder, + @Nullable AwsClientProperties clientProperties, @Nullable AwsConnectionDetails connectionDetails, + @Nullable io.awspring.cloud.autoconfigure.core.AwsClientCustomizer customizer, + @Nullable Stream> clientBuilderCustomizer, + @Nullable Stream commonBuilderCustomizer) { + T result = configure(builder, clientProperties, connectionDetails, customizer); + if (commonBuilderCustomizer != null && builder instanceof AwsSyncClientBuilder) { + commonBuilderCustomizer.forEach(it -> it.customize((AwsSyncClientBuilder) result)); + } + if (clientBuilderCustomizer != null) { + clientBuilderCustomizer.forEach(it -> it.customize(result)); + } + return result; + } + + @Deprecated + public > T configureAsyncClient(T builder, + @Nullable AwsClientProperties clientProperties, @Nullable AwsConnectionDetails connectionDetails, + @Nullable io.awspring.cloud.autoconfigure.core.AwsClientCustomizer customizer, + @Nullable Stream> clientBuilderCustomizer, + @Nullable Stream commonBuilderCustomizer) { + T result = configure(builder, clientProperties, connectionDetails, customizer); + if (commonBuilderCustomizer != null && builder instanceof AwsAsyncClientBuilder) { + commonBuilderCustomizer.forEach(it -> it.customize((AwsAsyncClientBuilder) result)); + } + if (clientBuilderCustomizer != null) { + clientBuilderCustomizer.forEach(it -> it.customize(result)); + } + return result; + } + public Region resolveRegion(@Nullable AwsClientProperties clientProperties, @Nullable AwsConnectionDetails connectionDetails) { return resolveRegion(clientProperties, connectionDetails, this.regionProvider); diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/core/AwsClientCustomizer.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/core/AwsClientCustomizer.java index 7f294dc39..a67990546 100644 --- a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/core/AwsClientCustomizer.java +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/core/AwsClientCustomizer.java @@ -27,6 +27,7 @@ * @author Matej Nedić * @since 3.0.0 */ +@Deprecated(since = "3.3.0", forRemoval = true) public interface AwsClientCustomizer { @Nullable diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/dynamodb/DynamoDbAutoConfiguration.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/dynamodb/DynamoDbAutoConfiguration.java index f6eeda0ce..7d67f80cc 100644 --- a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/dynamodb/DynamoDbAutoConfiguration.java +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/dynamodb/DynamoDbAutoConfiguration.java @@ -15,6 +15,7 @@ */ package io.awspring.cloud.autoconfigure.dynamodb; +import io.awspring.cloud.autoconfigure.AwsSyncClientCustomizer; import io.awspring.cloud.autoconfigure.core.AwsClientBuilderConfigurer; import io.awspring.cloud.autoconfigure.core.AwsClientCustomizer; import io.awspring.cloud.autoconfigure.core.AwsConnectionDetails; @@ -114,9 +115,12 @@ static class StandardDynamoDbClient { @Bean public DynamoDbClient dynamoDbClient(AwsClientBuilderConfigurer awsClientBuilderConfigurer, ObjectProvider> configurer, - ObjectProvider connectionDetails, DynamoDbProperties properties) { - return awsClientBuilderConfigurer.configure(DynamoDbClient.builder(), properties, - connectionDetails.getIfAvailable(), configurer.getIfAvailable()).build(); + ObjectProvider connectionDetails, DynamoDbProperties properties, + ObjectProvider dynamoDbClientCustomizers, + ObjectProvider awsSyncClientCustomizers) { + return awsClientBuilderConfigurer.configureSyncClient(DynamoDbClient.builder(), properties, + connectionDetails.getIfAvailable(), configurer.getIfAvailable(), + dynamoDbClientCustomizers.orderedStream(), awsSyncClientCustomizers.orderedStream()).build(); } } diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/dynamodb/DynamoDbClientCustomizer.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/dynamodb/DynamoDbClientCustomizer.java new file mode 100644 index 000000000..5db8e86d0 --- /dev/null +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/dynamodb/DynamoDbClientCustomizer.java @@ -0,0 +1,29 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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 io.awspring.cloud.autoconfigure.dynamodb; + +import io.awspring.cloud.autoconfigure.AwsClientCustomizer; +import software.amazon.awssdk.services.dynamodb.DynamoDbClientBuilder; + +/** + * Callback interface that can be used to customize a {@link DynamoDbClientBuilder}. + * + * @author Maciej Walkowiak + * @since 3.3.0 + */ +@FunctionalInterface +public interface DynamoDbClientCustomizer extends AwsClientCustomizer { +} diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/metrics/CloudWatchAsyncClientCustomizer.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/metrics/CloudWatchAsyncClientCustomizer.java new file mode 100644 index 000000000..cdc41336d --- /dev/null +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/metrics/CloudWatchAsyncClientCustomizer.java @@ -0,0 +1,29 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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 io.awspring.cloud.autoconfigure.metrics; + +import io.awspring.cloud.autoconfigure.AwsClientCustomizer; +import software.amazon.awssdk.services.cloudwatch.CloudWatchAsyncClientBuilder; + +/** + * Callback interface that can be used to customize a {@link CloudWatchAsyncClientBuilder}. + * + * @author Maciej Walkowiak + * @since 3.3.0 + */ +@FunctionalInterface +public interface CloudWatchAsyncClientCustomizer extends AwsClientCustomizer { +} diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/metrics/CloudWatchExportAutoConfiguration.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/metrics/CloudWatchExportAutoConfiguration.java index 218f709fd..aca3bdc75 100644 --- a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/metrics/CloudWatchExportAutoConfiguration.java +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/metrics/CloudWatchExportAutoConfiguration.java @@ -15,6 +15,7 @@ */ package io.awspring.cloud.autoconfigure.metrics; +import io.awspring.cloud.autoconfigure.AwsAsyncClientCustomizer; import io.awspring.cloud.autoconfigure.core.AwsClientBuilderConfigurer; import io.awspring.cloud.autoconfigure.core.AwsClientCustomizer; import io.awspring.cloud.autoconfigure.core.AwsConnectionDetails; @@ -69,9 +70,12 @@ public CloudWatchMeterRegistry cloudWatchMeterRegistry(CloudWatchConfig config, public CloudWatchAsyncClient cloudWatchAsyncClient(CloudWatchProperties properties, AwsClientBuilderConfigurer awsClientBuilderConfigurer, ObjectProvider> configurer, - ObjectProvider connectionDetails) { - return awsClientBuilderConfigurer.configure(CloudWatchAsyncClient.builder(), properties, - connectionDetails.getIfAvailable(), configurer.getIfAvailable()).build(); + ObjectProvider connectionDetails, + ObjectProvider cloudWatchAsyncClientCustomizers, + ObjectProvider awsAsyncClientCustomizers) { + return awsClientBuilderConfigurer.configureAsyncClient(CloudWatchAsyncClient.builder(), properties, + connectionDetails.getIfAvailable(), configurer.getIfAvailable(), + cloudWatchAsyncClientCustomizers.orderedStream(), awsAsyncClientCustomizers.orderedStream()).build(); } @Bean diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/s3/S3AutoConfiguration.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/s3/S3AutoConfiguration.java index 9f37e261b..ceb0295d0 100644 --- a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/s3/S3AutoConfiguration.java +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/s3/S3AutoConfiguration.java @@ -16,6 +16,7 @@ package io.awspring.cloud.autoconfigure.s3; import com.fasterxml.jackson.databind.ObjectMapper; +import io.awspring.cloud.autoconfigure.AwsSyncClientCustomizer; import io.awspring.cloud.autoconfigure.core.AwsClientBuilderConfigurer; import io.awspring.cloud.autoconfigure.core.AwsClientCustomizer; import io.awspring.cloud.autoconfigure.core.AwsConnectionDetails; @@ -70,9 +71,12 @@ public S3AutoConfiguration(S3Properties properties) { @ConditionalOnMissingBean S3ClientBuilder s3ClientBuilder(AwsClientBuilderConfigurer awsClientBuilderConfigurer, ObjectProvider> configurer, - ObjectProvider connectionDetails) { - S3ClientBuilder builder = awsClientBuilderConfigurer.configure(S3Client.builder(), this.properties, - connectionDetails.getIfAvailable(), configurer.getIfAvailable()); + ObjectProvider connectionDetails, + ObjectProvider s3ClientCustomizers, + ObjectProvider awsSyncClientCustomizers) { + S3ClientBuilder builder = awsClientBuilderConfigurer.configureSyncClient(S3Client.builder(), this.properties, + connectionDetails.getIfAvailable(), configurer.getIfAvailable(), s3ClientCustomizers.orderedStream(), + awsSyncClientCustomizers.orderedStream()); Optional.ofNullable(this.properties.getCrossRegionEnabled()).ifPresent(builder::crossRegionAccessEnabled); diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/s3/S3ClientCustomizer.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/s3/S3ClientCustomizer.java new file mode 100644 index 000000000..dbeae9ec6 --- /dev/null +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/s3/S3ClientCustomizer.java @@ -0,0 +1,29 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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 io.awspring.cloud.autoconfigure.s3; + +import io.awspring.cloud.autoconfigure.AwsClientCustomizer; +import software.amazon.awssdk.services.s3.S3ClientBuilder; + +/** + * Callback interface that can be used to customize a {@link S3ClientBuilder}. + * + * @author Maciej Walkowiak + * @since 3.3.0 + */ +@FunctionalInterface +public interface S3ClientCustomizer extends AwsClientCustomizer { +} diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/ses/SesAutoConfiguration.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/ses/SesAutoConfiguration.java index 4166384ae..91b7e070f 100644 --- a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/ses/SesAutoConfiguration.java +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/ses/SesAutoConfiguration.java @@ -15,6 +15,7 @@ */ package io.awspring.cloud.autoconfigure.ses; +import io.awspring.cloud.autoconfigure.AwsSyncClientCustomizer; import io.awspring.cloud.autoconfigure.core.AwsClientBuilderConfigurer; import io.awspring.cloud.autoconfigure.core.AwsClientCustomizer; import io.awspring.cloud.autoconfigure.core.AwsConnectionDetails; @@ -56,9 +57,12 @@ public class SesAutoConfiguration { @ConditionalOnMissingBean public SesClient sesClient(SesProperties properties, AwsClientBuilderConfigurer awsClientBuilderConfigurer, ObjectProvider> configurer, - ObjectProvider connectionDetails) { - return awsClientBuilderConfigurer.configure(SesClient.builder(), properties, connectionDetails.getIfAvailable(), - configurer.getIfAvailable()).build(); + ObjectProvider connectionDetails, + ObjectProvider sesClientCustomizers, + ObjectProvider awsSyncClientCustomizers) { + return awsClientBuilderConfigurer.configureSyncClient(SesClient.builder(), properties, + connectionDetails.getIfAvailable(), configurer.getIfAvailable(), sesClientCustomizers.orderedStream(), + awsSyncClientCustomizers.orderedStream()).build(); } @Bean diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/ses/SesClientCustomizer.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/ses/SesClientCustomizer.java new file mode 100644 index 000000000..ec85f1224 --- /dev/null +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/ses/SesClientCustomizer.java @@ -0,0 +1,29 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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 io.awspring.cloud.autoconfigure.ses; + +import io.awspring.cloud.autoconfigure.AwsClientCustomizer; +import software.amazon.awssdk.services.ses.SesClientBuilder; + +/** + * Callback interface that can be used to customize a {@link SesClientBuilder}. + * + * @author Maciej Walkowiak + * @since 3.3.0 + */ +@FunctionalInterface +public interface SesClientCustomizer extends AwsClientCustomizer { +} diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/sns/SnsAutoConfiguration.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/sns/SnsAutoConfiguration.java index 1b308dc52..882a4b0ea 100644 --- a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/sns/SnsAutoConfiguration.java +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/sns/SnsAutoConfiguration.java @@ -18,6 +18,7 @@ import static io.awspring.cloud.sns.configuration.NotificationHandlerMethodArgumentResolverConfigurationUtils.getNotificationHandlerMethodArgumentResolver; import com.fasterxml.jackson.databind.ObjectMapper; +import io.awspring.cloud.autoconfigure.AwsSyncClientCustomizer; import io.awspring.cloud.autoconfigure.core.AwsClientBuilderConfigurer; import io.awspring.cloud.autoconfigure.core.AwsClientCustomizer; import io.awspring.cloud.autoconfigure.core.AwsConnectionDetails; @@ -69,9 +70,12 @@ public class SnsAutoConfiguration { @Bean public SnsClient snsClient(SnsProperties properties, AwsClientBuilderConfigurer awsClientBuilderConfigurer, ObjectProvider> configurer, - ObjectProvider connectionDetails) { - return awsClientBuilderConfigurer.configure(SnsClient.builder(), properties, connectionDetails.getIfAvailable(), - configurer.getIfAvailable()).build(); + ObjectProvider connectionDetails, + ObjectProvider snsClientCustomizers, + ObjectProvider awsSyncClientCustomizers) { + return awsClientBuilderConfigurer.configureSyncClient(SnsClient.builder(), properties, + connectionDetails.getIfAvailable(), configurer.getIfAvailable(), snsClientCustomizers.orderedStream(), + awsSyncClientCustomizers.orderedStream()).build(); } @ConditionalOnMissingBean(SnsOperations.class) diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/sns/SnsClientCustomizer.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/sns/SnsClientCustomizer.java new file mode 100644 index 000000000..e9f82ac2d --- /dev/null +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/sns/SnsClientCustomizer.java @@ -0,0 +1,29 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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 io.awspring.cloud.autoconfigure.sns; + +import io.awspring.cloud.autoconfigure.AwsClientCustomizer; +import software.amazon.awssdk.services.sns.SnsClientBuilder; + +/** + * Callback interface that can be used to customize a {@link SnsClientBuilder}. + * + * @author Maciej Walkowiak + * @since 3.3.0 + */ +@FunctionalInterface +public interface SnsClientCustomizer extends AwsClientCustomizer { +} diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/sqs/SqsAsyncClientCustomizer.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/sqs/SqsAsyncClientCustomizer.java new file mode 100644 index 000000000..2889bd493 --- /dev/null +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/sqs/SqsAsyncClientCustomizer.java @@ -0,0 +1,29 @@ +/* + * Copyright 2013-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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 io.awspring.cloud.autoconfigure.sqs; + +import io.awspring.cloud.autoconfigure.AwsClientCustomizer; +import software.amazon.awssdk.services.sqs.SqsAsyncClientBuilder; + +/** + * Callback interface that can be used to customize a {@link SqsAsyncClientBuilder}. + * + * @author Maciej Walkowiak + * @since 3.3.0 + */ +@FunctionalInterface +public interface SqsAsyncClientCustomizer extends AwsClientCustomizer { +} diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/sqs/SqsAutoConfiguration.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/sqs/SqsAutoConfiguration.java index adb41616a..c06b65b29 100644 --- a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/sqs/SqsAutoConfiguration.java +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/sqs/SqsAutoConfiguration.java @@ -16,6 +16,7 @@ package io.awspring.cloud.autoconfigure.sqs; import com.fasterxml.jackson.databind.ObjectMapper; +import io.awspring.cloud.autoconfigure.AwsAsyncClientCustomizer; import io.awspring.cloud.autoconfigure.core.AwsClientBuilderConfigurer; import io.awspring.cloud.autoconfigure.core.AwsClientCustomizer; import io.awspring.cloud.autoconfigure.core.AwsConnectionDetails; @@ -75,9 +76,12 @@ public SqsAutoConfiguration(SqsProperties sqsProperties) { @Bean public SqsAsyncClient sqsAsyncClient(AwsClientBuilderConfigurer awsClientBuilderConfigurer, ObjectProvider> configurer, - ObjectProvider connectionDetails) { - return awsClientBuilderConfigurer.configure(SqsAsyncClient.builder(), this.sqsProperties, - connectionDetails.getIfAvailable(), configurer.getIfAvailable()).build(); + ObjectProvider connectionDetails, + ObjectProvider sqsAsyncClientCustomizers, + ObjectProvider awsAsyncClientCustomizers) { + return awsClientBuilderConfigurer.configureAsyncClient(SqsAsyncClient.builder(), this.sqsProperties, + connectionDetails.getIfAvailable(), configurer.getIfAvailable(), + sqsAsyncClientCustomizers.orderedStream(), awsAsyncClientCustomizers.orderedStream()).build(); } @ConditionalOnMissingBean diff --git a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/ConfiguredAwsClient.java b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/ConfiguredAwsClient.java index f14af9fa1..a4cb66ec7 100644 --- a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/ConfiguredAwsClient.java +++ b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/ConfiguredAwsClient.java @@ -66,6 +66,10 @@ public Duration getApiCallTimeout() { return clientConfigurationAttributes.get(SdkClientOption.API_CALL_TIMEOUT); } + public Duration getApiCallAttemptTimeout() { + return clientConfigurationAttributes.get(SdkClientOption.API_CALL_ATTEMPT_TIMEOUT); + } + public SdkHttpClient getSyncHttpClient() { return clientConfigurationAttributes.get(SdkClientOption.SYNC_HTTP_CLIENT); } diff --git a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/config/parameterstore/ParameterStoreConfigDataLoaderIntegrationTests.java b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/config/parameterstore/ParameterStoreConfigDataLoaderIntegrationTests.java index e3aabf0d1..60a488005 100644 --- a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/config/parameterstore/ParameterStoreConfigDataLoaderIntegrationTests.java +++ b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/config/parameterstore/ParameterStoreConfigDataLoaderIntegrationTests.java @@ -22,6 +22,7 @@ import static org.mockito.Mockito.when; import static org.testcontainers.shaded.org.awaitility.Awaitility.await; +import io.awspring.cloud.autoconfigure.AwsSyncClientCustomizer; import io.awspring.cloud.autoconfigure.ConfiguredAwsClient; import java.io.IOException; import java.time.Duration; @@ -138,6 +139,20 @@ void clientIsConfiguredWithConfigurerProvidedToBootstrapRegistry() { } } + @Test + void clientIsConfiguredWithCustomizerProvidedToBootstrapRegistry() { + SpringApplication application = new SpringApplication(App.class); + application.setWebApplicationType(WebApplicationType.NONE); + application.addBootstrapRegistryInitializer(new CustomizerConfiguration()); + + try (ConfigurableApplicationContext context = runApplication(application, + "aws-parameterstore:/config/spring/")) { + ConfiguredAwsClient ssmClient = new ConfiguredAwsClient(context.getBean(SsmClient.class)); + assertThat(ssmClient.getApiCallTimeout()).isEqualTo(Duration.ofMillis(2001)); + assertThat(ssmClient.getSyncHttpClient()).isNotNull(); + } + } + @Test void whenKeysAreNotSpecifiedFailsWithHumanReadableFailureMessage(CapturedOutput output) { SpringApplication application = new SpringApplication(App.class); @@ -451,4 +466,19 @@ public SdkHttpClient httpClient() { } } + static class CustomizerConfiguration implements BootstrapRegistryInitializer { + + @Override + public void initialize(BootstrapRegistry registry) { + registry.register(SsmClientCustomizer.class, context -> (builder -> { + builder.overrideConfiguration(builder.overrideConfiguration().copy(c -> { + c.apiCallTimeout(Duration.ofMillis(2001)); + })); + })); + registry.register(AwsSyncClientCustomizer.class, context -> (builder -> { + builder.httpClient(ApacheHttpClient.builder().connectionTimeout(Duration.ofMillis(1542)).build()); + })); + } + } + } diff --git a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/config/parameterstore/SsmClientCustomizerTests.java b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/config/parameterstore/SsmClientCustomizerTests.java new file mode 100644 index 000000000..677a4e7f2 --- /dev/null +++ b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/config/parameterstore/SsmClientCustomizerTests.java @@ -0,0 +1,124 @@ +/* + * Copyright 2013-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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 io.awspring.cloud.autoconfigure.config.parameterstore; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.awspring.cloud.autoconfigure.AwsSyncClientCustomizer; +import io.awspring.cloud.autoconfigure.ConfiguredAwsClient; +import io.awspring.cloud.autoconfigure.core.AwsAutoConfiguration; +import io.awspring.cloud.autoconfigure.core.CredentialsProviderAutoConfiguration; +import io.awspring.cloud.autoconfigure.core.RegionProviderAutoConfiguration; +import java.time.Duration; +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; +import software.amazon.awssdk.http.apache.ApacheHttpClient; +import software.amazon.awssdk.services.ssm.SsmClient; + +/** + * Tests for {@link SsmClientCustomizer}. + * + * @author Maciej Walkowiak + */ +class SsmClientCustomizerTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withPropertyValues("spring.cloud.aws.region.static:eu-west-1", + "spring.cloud.aws.credentials.access-key:noop", "spring.cloud.aws.credentials.secret-key:noop") + .withConfiguration(AutoConfigurations.of(AwsAutoConfiguration.class, RegionProviderAutoConfiguration.class, + CredentialsProviderAutoConfiguration.class, ParameterStoreAutoConfiguration.class)); + + @Test + void customClientCustomizer() { + contextRunner.withUserConfiguration(CustomizerConfig.class).run(context -> { + ConfiguredAwsClient client = new ConfiguredAwsClient(context.getBean(SsmClient.class)); + assertThat(client.getApiCallTimeout()).describedAs("sets property from first customizer") + .isEqualTo(Duration.ofMillis(2001)); + assertThat(client.getApiCallAttemptTimeout()).describedAs("sets property from second customizer") + .isEqualTo(Duration.ofMillis(2002)); + assertThat(client.getSyncHttpClient()).describedAs("sets property from common client customizer") + .isNotNull(); + }); + } + + @Test + void customClientCustomizerWithOrder() { + contextRunner.withUserConfiguration(CustomizerConfigWithOrder.class).run(context -> { + ConfiguredAwsClient client = new ConfiguredAwsClient(context.getBean(SsmClient.class)); + assertThat(client.getApiCallTimeout()) + .describedAs("property from the customizer with higher order takes precedence") + .isEqualTo(Duration.ofMillis(2001)); + }); + } + + @Configuration(proxyBeanMethods = false) + static class CustomizerConfig { + + @Bean + SsmClientCustomizer customizer() { + return builder -> { + builder.overrideConfiguration(builder.overrideConfiguration().copy(c -> { + c.apiCallTimeout(Duration.ofMillis(2001)); + })); + }; + } + + @Bean + SsmClientCustomizer customizer2() { + return builder -> { + builder.overrideConfiguration(builder.overrideConfiguration().copy(c -> { + c.apiCallAttemptTimeout(Duration.ofMillis(2002)); + })); + }; + } + + @Bean + AwsSyncClientCustomizer awsSyncClientCustomizer() { + return builder -> { + builder.httpClient(ApacheHttpClient.builder().connectionTimeout(Duration.ofMillis(1542)).build()); + }; + } + } + + @Configuration(proxyBeanMethods = false) + static class CustomizerConfigWithOrder { + + @Bean + @Order(2) + SsmClientCustomizer customizer() { + return builder -> { + builder.overrideConfiguration(builder.overrideConfiguration().copy(c -> { + c.apiCallTimeout(Duration.ofMillis(2001)); + })); + }; + } + + @Bean + @Order(1) + SsmClientCustomizer customizer2() { + return builder -> { + builder.overrideConfiguration(builder.overrideConfiguration().copy(c -> { + c.apiCallTimeout(Duration.ofMillis(2000)); + })); + }; + } + } + +} diff --git a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/config/secretsmanager/SecretsManagerClientCustomizerTests.java b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/config/secretsmanager/SecretsManagerClientCustomizerTests.java new file mode 100644 index 000000000..de79007f4 --- /dev/null +++ b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/config/secretsmanager/SecretsManagerClientCustomizerTests.java @@ -0,0 +1,124 @@ +/* + * Copyright 2013-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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 io.awspring.cloud.autoconfigure.config.secretsmanager; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.awspring.cloud.autoconfigure.AwsSyncClientCustomizer; +import io.awspring.cloud.autoconfigure.ConfiguredAwsClient; +import io.awspring.cloud.autoconfigure.core.AwsAutoConfiguration; +import io.awspring.cloud.autoconfigure.core.CredentialsProviderAutoConfiguration; +import io.awspring.cloud.autoconfigure.core.RegionProviderAutoConfiguration; +import java.time.Duration; +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; +import software.amazon.awssdk.http.apache.ApacheHttpClient; +import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient; + +/** + * Tests for {@link SecretsManagerClientCustomizer}. + * + * @author Maciej Walkowiak + */ +class SecretsManagerClientCustomizerTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withPropertyValues("spring.cloud.aws.region.static:eu-west-1", + "spring.cloud.aws.credentials.access-key:noop", "spring.cloud.aws.credentials.secret-key:noop") + .withConfiguration(AutoConfigurations.of(AwsAutoConfiguration.class, RegionProviderAutoConfiguration.class, + CredentialsProviderAutoConfiguration.class, SecretsManagerAutoConfiguration.class)); + + @Test + void customClientCustomizer() { + contextRunner.withUserConfiguration(CustomizerConfig.class).run(context -> { + ConfiguredAwsClient client = new ConfiguredAwsClient(context.getBean(SecretsManagerClient.class)); + assertThat(client.getApiCallTimeout()).describedAs("sets property from first customizer") + .isEqualTo(Duration.ofMillis(2001)); + assertThat(client.getApiCallAttemptTimeout()).describedAs("sets property from second customizer") + .isEqualTo(Duration.ofMillis(2002)); + assertThat(client.getSyncHttpClient()).describedAs("sets property from common client customizer") + .isNotNull(); + }); + } + + @Test + void customClientCustomizerWithOrder() { + contextRunner.withUserConfiguration(CustomizerConfigWithOrder.class).run(context -> { + ConfiguredAwsClient client = new ConfiguredAwsClient(context.getBean(SecretsManagerClient.class)); + assertThat(client.getApiCallTimeout()) + .describedAs("property from the customizer with higher order takes precedence") + .isEqualTo(Duration.ofMillis(2001)); + }); + } + + @Configuration(proxyBeanMethods = false) + static class CustomizerConfig { + + @Bean + SecretsManagerClientCustomizer customizer() { + return builder -> { + builder.overrideConfiguration(builder.overrideConfiguration().copy(c -> { + c.apiCallTimeout(Duration.ofMillis(2001)); + })); + }; + } + + @Bean + SecretsManagerClientCustomizer customizer2() { + return builder -> { + builder.overrideConfiguration(builder.overrideConfiguration().copy(c -> { + c.apiCallAttemptTimeout(Duration.ofMillis(2002)); + })); + }; + } + + @Bean + AwsSyncClientCustomizer awsSyncClientCustomizer() { + return builder -> { + builder.httpClient(ApacheHttpClient.builder().connectionTimeout(Duration.ofMillis(1542)).build()); + }; + } + } + + @Configuration(proxyBeanMethods = false) + static class CustomizerConfigWithOrder { + + @Bean + @Order(2) + SecretsManagerClientCustomizer customizer() { + return builder -> { + builder.overrideConfiguration(builder.overrideConfiguration().copy(c -> { + c.apiCallTimeout(Duration.ofMillis(2001)); + })); + }; + } + + @Bean + @Order(1) + SecretsManagerClientCustomizer customizer2() { + return builder -> { + builder.overrideConfiguration(builder.overrideConfiguration().copy(c -> { + c.apiCallTimeout(Duration.ofMillis(2000)); + })); + }; + } + } + +} diff --git a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/config/secretsmanager/SecretsManagerConfigDataLoaderIntegrationTests.java b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/config/secretsmanager/SecretsManagerConfigDataLoaderIntegrationTests.java index 791e78e9d..27e898315 100644 --- a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/config/secretsmanager/SecretsManagerConfigDataLoaderIntegrationTests.java +++ b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/config/secretsmanager/SecretsManagerConfigDataLoaderIntegrationTests.java @@ -22,6 +22,7 @@ import static org.mockito.Mockito.when; import static org.testcontainers.shaded.org.awaitility.Awaitility.await; +import io.awspring.cloud.autoconfigure.AwsSyncClientCustomizer; import io.awspring.cloud.autoconfigure.ConfiguredAwsClient; import java.io.File; import java.io.IOException; @@ -193,6 +194,20 @@ void clientIsConfiguredWithConfigurerProvidedToBootstrapRegistry() { } } + @Test + void clientIsConfiguredWithCustomizerProvidedToBootstrapRegistry() { + SpringApplication application = new SpringApplication(App.class); + application.setWebApplicationType(WebApplicationType.NONE); + application.addBootstrapRegistryInitializer(new CustomizerConfiguration()); + + try (ConfigurableApplicationContext context = runApplication(application, + "aws-secretsmanager:/config/spring;/config/second")) { + ConfiguredAwsClient client = new ConfiguredAwsClient(context.getBean(SecretsManagerClient.class)); + assertThat(client.getApiCallTimeout()).isEqualTo(Duration.ofMillis(2001)); + assertThat(client.getSyncHttpClient()).isNotNull(); + } + } + @Test void whenKeysAreNotSpecifiedFailsWithHumanReadableFailureMessage(CapturedOutput output) { SpringApplication application = new SpringApplication(App.class); @@ -501,4 +516,19 @@ public SdkHttpClient httpClient() { } } + static class CustomizerConfiguration implements BootstrapRegistryInitializer { + + @Override + public void initialize(BootstrapRegistry registry) { + registry.register(SecretsManagerClientCustomizer.class, context -> (builder -> { + builder.overrideConfiguration(builder.overrideConfiguration().copy(c -> { + c.apiCallTimeout(Duration.ofMillis(2001)); + })); + })); + registry.register(AwsSyncClientCustomizer.class, context -> (builder -> { + builder.httpClient(ApacheHttpClient.builder().connectionTimeout(Duration.ofMillis(1542)).build()); + })); + } + } + } diff --git a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/dynamodb/DynamoDbClientCustomizerTests.java b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/dynamodb/DynamoDbClientCustomizerTests.java new file mode 100644 index 000000000..3867f3d68 --- /dev/null +++ b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/dynamodb/DynamoDbClientCustomizerTests.java @@ -0,0 +1,124 @@ +/* + * Copyright 2013-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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 io.awspring.cloud.autoconfigure.dynamodb; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.awspring.cloud.autoconfigure.AwsSyncClientCustomizer; +import io.awspring.cloud.autoconfigure.ConfiguredAwsClient; +import io.awspring.cloud.autoconfigure.core.AwsAutoConfiguration; +import io.awspring.cloud.autoconfigure.core.CredentialsProviderAutoConfiguration; +import io.awspring.cloud.autoconfigure.core.RegionProviderAutoConfiguration; +import java.time.Duration; +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; +import software.amazon.awssdk.http.apache.ApacheHttpClient; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; + +/** + * Tests for {@link DynamoDbClientCustomizer}. + * + * @author Maciej Walkowiak + */ +class DynamoDbClientCustomizerTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withPropertyValues("spring.cloud.aws.region.static:eu-west-1", + "spring.cloud.aws.credentials.access-key:noop", "spring.cloud.aws.credentials.secret-key:noop") + .withConfiguration(AutoConfigurations.of(AwsAutoConfiguration.class, RegionProviderAutoConfiguration.class, + CredentialsProviderAutoConfiguration.class, DynamoDbAutoConfiguration.class)); + + @Test + void customDynamoDbClientCustomizer() { + contextRunner.withUserConfiguration(CustomizerConfig.class).run(context -> { + ConfiguredAwsClient dynamoDbClient = new ConfiguredAwsClient(context.getBean(DynamoDbClient.class)); + assertThat(dynamoDbClient.getApiCallTimeout()).describedAs("sets property from first customizer") + .isEqualTo(Duration.ofMillis(2001)); + assertThat(dynamoDbClient.getApiCallAttemptTimeout()).describedAs("sets property from second customizer") + .isEqualTo(Duration.ofMillis(2002)); + assertThat(dynamoDbClient.getSyncHttpClient()).describedAs("sets property from common client customizer") + .isNotNull(); + }); + } + + @Test + void customDynamoDbClientCustomizerWithOrder() { + contextRunner.withUserConfiguration(CustomizerConfigWithOrder.class).run(context -> { + ConfiguredAwsClient dynamoDbClient = new ConfiguredAwsClient(context.getBean(DynamoDbClient.class)); + assertThat(dynamoDbClient.getApiCallTimeout()) + .describedAs("property from the customizer with higher order takes precedence") + .isEqualTo(Duration.ofMillis(2001)); + }); + } + + @Configuration(proxyBeanMethods = false) + static class CustomizerConfig { + + @Bean + DynamoDbClientCustomizer dynamoDbClientCustomizer() { + return builder -> { + builder.overrideConfiguration(builder.overrideConfiguration().copy(c -> { + c.apiCallTimeout(Duration.ofMillis(2001)); + })); + }; + } + + @Bean + DynamoDbClientCustomizer dynamoDbClientCustomizer2() { + return builder -> { + builder.overrideConfiguration(builder.overrideConfiguration().copy(c -> { + c.apiCallAttemptTimeout(Duration.ofMillis(2002)); + })); + }; + } + + @Bean + AwsSyncClientCustomizer commonAwsSyncClientCustomizer() { + return builder -> { + builder.httpClient(ApacheHttpClient.builder().connectionTimeout(Duration.ofMillis(1542)).build()); + }; + } + } + + @Configuration(proxyBeanMethods = false) + static class CustomizerConfigWithOrder { + + @Bean + @Order(2) + DynamoDbClientCustomizer dynamoDbClientCustomizer() { + return builder -> { + builder.overrideConfiguration(builder.overrideConfiguration().copy(c -> { + c.apiCallTimeout(Duration.ofMillis(2001)); + })); + }; + } + + @Bean + @Order(1) + DynamoDbClientCustomizer dynamoDbClientCustomizer2() { + return builder -> { + builder.overrideConfiguration(builder.overrideConfiguration().copy(c -> { + c.apiCallTimeout(Duration.ofMillis(2000)); + })); + }; + } + } + +} diff --git a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/metrics/CloudWatchAsyncClientCustomizerTests.java b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/metrics/CloudWatchAsyncClientCustomizerTests.java new file mode 100644 index 000000000..ebdec6c4c --- /dev/null +++ b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/metrics/CloudWatchAsyncClientCustomizerTests.java @@ -0,0 +1,126 @@ +/* + * Copyright 2013-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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 io.awspring.cloud.autoconfigure.metrics; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.awspring.cloud.autoconfigure.AwsAsyncClientCustomizer; +import io.awspring.cloud.autoconfigure.ConfiguredAwsClient; +import io.awspring.cloud.autoconfigure.core.AwsAutoConfiguration; +import io.awspring.cloud.autoconfigure.core.CredentialsProviderAutoConfiguration; +import io.awspring.cloud.autoconfigure.core.RegionProviderAutoConfiguration; +import java.time.Duration; +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; +import software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClient; +import software.amazon.awssdk.services.cloudwatch.CloudWatchAsyncClient; + +/** + * Tests for {@link CloudWatchAsyncClientCustomizer}. + * + * @author Maciej Walkowiak + */ +class CloudWatchAsyncClientCustomizerTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withPropertyValues("spring.cloud.aws.region.static:eu-west-1", + "spring.cloud.aws.credentials.access-key:noop", "spring.cloud.aws.credentials.secret-key:noop") + .withConfiguration(AutoConfigurations.of(AwsAutoConfiguration.class, RegionProviderAutoConfiguration.class, + CredentialsProviderAutoConfiguration.class, CloudWatchExportAutoConfiguration.class)) + .withPropertyValues("management.cloudwatch.metrics.export.namespace:test"); + + @Test + void customClientCustomizer() { + contextRunner.withUserConfiguration(CustomizerConfig.class).run(context -> { + ConfiguredAwsClient client = new ConfiguredAwsClient(context.getBean(CloudWatchAsyncClient.class)); + assertThat(client.getApiCallTimeout()).describedAs("sets property from first customizer") + .isEqualTo(Duration.ofMillis(2001)); + assertThat(client.getApiCallAttemptTimeout()).describedAs("sets property from second customizer") + .isEqualTo(Duration.ofMillis(2002)); + assertThat(client.getAsyncHttpClient()).describedAs("sets property from common client customizer") + .isNotNull(); + }); + } + + @Test + void customClientCustomizerWithOrder() { + contextRunner.withUserConfiguration(CustomizerConfigWithOrder.class).run(context -> { + ConfiguredAwsClient client = new ConfiguredAwsClient(context.getBean(CloudWatchAsyncClient.class)); + assertThat(client.getApiCallTimeout()) + .describedAs("property from the customizer with higher order takes precedence") + .isEqualTo(Duration.ofMillis(2001)); + }); + } + + @Configuration(proxyBeanMethods = false) + static class CustomizerConfig { + + @Bean + CloudWatchAsyncClientCustomizer customizer() { + return builder -> { + builder.overrideConfiguration(builder.overrideConfiguration().copy(c -> { + c.apiCallTimeout(Duration.ofMillis(2001)); + })); + }; + } + + @Bean + CloudWatchAsyncClientCustomizer customizer2() { + return builder -> { + builder.overrideConfiguration(builder.overrideConfiguration().copy(c -> { + c.apiCallAttemptTimeout(Duration.ofMillis(2002)); + })); + }; + } + + @Bean + AwsAsyncClientCustomizer awsAsyncClientCustomizer() { + return builder -> { + builder.httpClient( + NettyNioAsyncHttpClient.builder().connectionTimeout(Duration.ofMillis(1542)).build()); + }; + } + } + + @Configuration(proxyBeanMethods = false) + static class CustomizerConfigWithOrder { + + @Bean + @Order(2) + CloudWatchAsyncClientCustomizer customizer() { + return builder -> { + builder.overrideConfiguration(builder.overrideConfiguration().copy(c -> { + c.apiCallTimeout(Duration.ofMillis(2001)); + })); + }; + } + + @Bean + @Order(1) + CloudWatchAsyncClientCustomizer customizer2() { + return builder -> { + builder.overrideConfiguration(builder.overrideConfiguration().copy(c -> { + c.apiCallTimeout(Duration.ofMillis(2000)); + })); + }; + } + } + +} diff --git a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/s3/S3ClientCustomizerTests.java b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/s3/S3ClientCustomizerTests.java new file mode 100644 index 000000000..2679effd9 --- /dev/null +++ b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/s3/S3ClientCustomizerTests.java @@ -0,0 +1,124 @@ +/* + * Copyright 2013-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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 io.awspring.cloud.autoconfigure.s3; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.awspring.cloud.autoconfigure.AwsSyncClientCustomizer; +import io.awspring.cloud.autoconfigure.ConfiguredAwsClient; +import io.awspring.cloud.autoconfigure.core.AwsAutoConfiguration; +import io.awspring.cloud.autoconfigure.core.CredentialsProviderAutoConfiguration; +import io.awspring.cloud.autoconfigure.core.RegionProviderAutoConfiguration; +import java.time.Duration; +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; +import software.amazon.awssdk.http.apache.ApacheHttpClient; +import software.amazon.awssdk.services.s3.S3Client; + +/** + * Tests for {@link S3ClientCustomizer}. + * + * @author Maciej Walkowiak + */ +class S3ClientCustomizerTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withPropertyValues("spring.cloud.aws.region.static:eu-west-1", + "spring.cloud.aws.credentials.access-key:noop", "spring.cloud.aws.credentials.secret-key:noop") + .withConfiguration(AutoConfigurations.of(AwsAutoConfiguration.class, RegionProviderAutoConfiguration.class, + CredentialsProviderAutoConfiguration.class, S3AutoConfiguration.class)); + + @Test + void customClientCustomizer() { + contextRunner.withUserConfiguration(CustomizerConfig.class).run(context -> { + ConfiguredAwsClient client = new ConfiguredAwsClient(context.getBean(S3Client.class)); + assertThat(client.getApiCallTimeout()).describedAs("sets property from first customizer") + .isEqualTo(Duration.ofMillis(2001)); + assertThat(client.getApiCallAttemptTimeout()).describedAs("sets property from second customizer") + .isEqualTo(Duration.ofMillis(2002)); + assertThat(client.getSyncHttpClient()).describedAs("sets property from common client customizer") + .isNotNull(); + }); + } + + @Test + void customClientCustomizerWithOrder() { + contextRunner.withUserConfiguration(CustomizerConfigWithOrder.class).run(context -> { + ConfiguredAwsClient client = new ConfiguredAwsClient(context.getBean(S3Client.class)); + assertThat(client.getApiCallTimeout()) + .describedAs("property from the customizer with higher order takes precedence") + .isEqualTo(Duration.ofMillis(2001)); + }); + } + + @Configuration(proxyBeanMethods = false) + static class CustomizerConfig { + + @Bean + S3ClientCustomizer customizer() { + return builder -> { + builder.overrideConfiguration(builder.overrideConfiguration().copy(c -> { + c.apiCallTimeout(Duration.ofMillis(2001)); + })); + }; + } + + @Bean + S3ClientCustomizer customizer2() { + return builder -> { + builder.overrideConfiguration(builder.overrideConfiguration().copy(c -> { + c.apiCallAttemptTimeout(Duration.ofMillis(2002)); + })); + }; + } + + @Bean + AwsSyncClientCustomizer awsSyncClientCustomizer() { + return builder -> { + builder.httpClient(ApacheHttpClient.builder().connectionTimeout(Duration.ofMillis(1542)).build()); + }; + } + } + + @Configuration(proxyBeanMethods = false) + static class CustomizerConfigWithOrder { + + @Bean + @Order(2) + S3ClientCustomizer customizer() { + return builder -> { + builder.overrideConfiguration(builder.overrideConfiguration().copy(c -> { + c.apiCallTimeout(Duration.ofMillis(2001)); + })); + }; + } + + @Bean + @Order(1) + S3ClientCustomizer customizer2() { + return builder -> { + builder.overrideConfiguration(builder.overrideConfiguration().copy(c -> { + c.apiCallTimeout(Duration.ofMillis(2000)); + })); + }; + } + } + +} diff --git a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/ses/SesClientCustomizerTests.java b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/ses/SesClientCustomizerTests.java new file mode 100644 index 000000000..4ec9f1c8e --- /dev/null +++ b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/ses/SesClientCustomizerTests.java @@ -0,0 +1,124 @@ +/* + * Copyright 2013-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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 io.awspring.cloud.autoconfigure.ses; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.awspring.cloud.autoconfigure.AwsSyncClientCustomizer; +import io.awspring.cloud.autoconfigure.ConfiguredAwsClient; +import io.awspring.cloud.autoconfigure.core.AwsAutoConfiguration; +import io.awspring.cloud.autoconfigure.core.CredentialsProviderAutoConfiguration; +import io.awspring.cloud.autoconfigure.core.RegionProviderAutoConfiguration; +import java.time.Duration; +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; +import software.amazon.awssdk.http.apache.ApacheHttpClient; +import software.amazon.awssdk.services.ses.SesClient; + +/** + * Tests for {@link SesClientCustomizer}. + * + * @author Maciej Walkowiak + */ +class SesClientCustomizerTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withPropertyValues("spring.cloud.aws.region.static:eu-west-1", + "spring.cloud.aws.credentials.access-key:noop", "spring.cloud.aws.credentials.secret-key:noop") + .withConfiguration(AutoConfigurations.of(AwsAutoConfiguration.class, RegionProviderAutoConfiguration.class, + CredentialsProviderAutoConfiguration.class, SesAutoConfiguration.class)); + + @Test + void customClientCustomizer() { + contextRunner.withUserConfiguration(CustomizerConfig.class).run(context -> { + ConfiguredAwsClient client = new ConfiguredAwsClient(context.getBean(SesClient.class)); + assertThat(client.getApiCallTimeout()).describedAs("sets property from first customizer") + .isEqualTo(Duration.ofMillis(2001)); + assertThat(client.getApiCallAttemptTimeout()).describedAs("sets property from second customizer") + .isEqualTo(Duration.ofMillis(2002)); + assertThat(client.getSyncHttpClient()).describedAs("sets property from common client customizer") + .isNotNull(); + }); + } + + @Test + void customClientCustomizerWithOrder() { + contextRunner.withUserConfiguration(CustomizerConfigWithOrder.class).run(context -> { + ConfiguredAwsClient client = new ConfiguredAwsClient(context.getBean(SesClient.class)); + assertThat(client.getApiCallTimeout()) + .describedAs("property from the customizer with higher order takes precedence") + .isEqualTo(Duration.ofMillis(2001)); + }); + } + + @Configuration(proxyBeanMethods = false) + static class CustomizerConfig { + + @Bean + SesClientCustomizer customizer() { + return builder -> { + builder.overrideConfiguration(builder.overrideConfiguration().copy(c -> { + c.apiCallTimeout(Duration.ofMillis(2001)); + })); + }; + } + + @Bean + SesClientCustomizer customizer2() { + return builder -> { + builder.overrideConfiguration(builder.overrideConfiguration().copy(c -> { + c.apiCallAttemptTimeout(Duration.ofMillis(2002)); + })); + }; + } + + @Bean + AwsSyncClientCustomizer awsSyncClientCustomizer() { + return builder -> { + builder.httpClient(ApacheHttpClient.builder().connectionTimeout(Duration.ofMillis(1542)).build()); + }; + } + } + + @Configuration(proxyBeanMethods = false) + static class CustomizerConfigWithOrder { + + @Bean + @Order(2) + SesClientCustomizer customizer() { + return builder -> { + builder.overrideConfiguration(builder.overrideConfiguration().copy(c -> { + c.apiCallTimeout(Duration.ofMillis(2001)); + })); + }; + } + + @Bean + @Order(1) + SesClientCustomizer customizer2() { + return builder -> { + builder.overrideConfiguration(builder.overrideConfiguration().copy(c -> { + c.apiCallTimeout(Duration.ofMillis(2000)); + })); + }; + } + } + +} diff --git a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/sns/SnsClientCustomizerTests.java b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/sns/SnsClientCustomizerTests.java new file mode 100644 index 000000000..4dc2180e4 --- /dev/null +++ b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/sns/SnsClientCustomizerTests.java @@ -0,0 +1,124 @@ +/* + * Copyright 2013-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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 io.awspring.cloud.autoconfigure.sns; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.awspring.cloud.autoconfigure.AwsSyncClientCustomizer; +import io.awspring.cloud.autoconfigure.ConfiguredAwsClient; +import io.awspring.cloud.autoconfigure.core.AwsAutoConfiguration; +import io.awspring.cloud.autoconfigure.core.CredentialsProviderAutoConfiguration; +import io.awspring.cloud.autoconfigure.core.RegionProviderAutoConfiguration; +import java.time.Duration; +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; +import software.amazon.awssdk.http.apache.ApacheHttpClient; +import software.amazon.awssdk.services.sns.SnsClient; + +/** + * Tests for {@link SnsClientCustomizer}. + * + * @author Maciej Walkowiak + */ +class SnsClientCustomizerTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withPropertyValues("spring.cloud.aws.region.static:eu-west-1", + "spring.cloud.aws.credentials.access-key:noop", "spring.cloud.aws.credentials.secret-key:noop") + .withConfiguration(AutoConfigurations.of(AwsAutoConfiguration.class, RegionProviderAutoConfiguration.class, + CredentialsProviderAutoConfiguration.class, SnsAutoConfiguration.class)); + + @Test + void customClientCustomizer() { + contextRunner.withUserConfiguration(CustomizerConfig.class).run(context -> { + ConfiguredAwsClient client = new ConfiguredAwsClient(context.getBean(SnsClient.class)); + assertThat(client.getApiCallTimeout()).describedAs("sets property from first customizer") + .isEqualTo(Duration.ofMillis(2001)); + assertThat(client.getApiCallAttemptTimeout()).describedAs("sets property from second customizer") + .isEqualTo(Duration.ofMillis(2002)); + assertThat(client.getSyncHttpClient()).describedAs("sets property from common client customizer") + .isNotNull(); + }); + } + + @Test + void customClientCustomizerWithOrder() { + contextRunner.withUserConfiguration(CustomizerConfigWithOrder.class).run(context -> { + ConfiguredAwsClient client = new ConfiguredAwsClient(context.getBean(SnsClient.class)); + assertThat(client.getApiCallTimeout()) + .describedAs("property from the customizer with higher order takes precedence") + .isEqualTo(Duration.ofMillis(2001)); + }); + } + + @Configuration(proxyBeanMethods = false) + static class CustomizerConfig { + + @Bean + SnsClientCustomizer customizer() { + return builder -> { + builder.overrideConfiguration(builder.overrideConfiguration().copy(c -> { + c.apiCallTimeout(Duration.ofMillis(2001)); + })); + }; + } + + @Bean + SnsClientCustomizer customizer2() { + return builder -> { + builder.overrideConfiguration(builder.overrideConfiguration().copy(c -> { + c.apiCallAttemptTimeout(Duration.ofMillis(2002)); + })); + }; + } + + @Bean + AwsSyncClientCustomizer awsSyncClientCustomizer() { + return builder -> { + builder.httpClient(ApacheHttpClient.builder().connectionTimeout(Duration.ofMillis(1542)).build()); + }; + } + } + + @Configuration(proxyBeanMethods = false) + static class CustomizerConfigWithOrder { + + @Bean + @Order(2) + SnsClientCustomizer customizer() { + return builder -> { + builder.overrideConfiguration(builder.overrideConfiguration().copy(c -> { + c.apiCallTimeout(Duration.ofMillis(2001)); + })); + }; + } + + @Bean + @Order(1) + SnsClientCustomizer customizer2() { + return builder -> { + builder.overrideConfiguration(builder.overrideConfiguration().copy(c -> { + c.apiCallTimeout(Duration.ofMillis(2000)); + })); + }; + } + } + +} diff --git a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/sqs/SqsAsyncClientCustomizerTests.java b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/sqs/SqsAsyncClientCustomizerTests.java new file mode 100644 index 000000000..4c393357f --- /dev/null +++ b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/sqs/SqsAsyncClientCustomizerTests.java @@ -0,0 +1,125 @@ +/* + * Copyright 2013-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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 io.awspring.cloud.autoconfigure.sqs; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.awspring.cloud.autoconfigure.AwsAsyncClientCustomizer; +import io.awspring.cloud.autoconfigure.ConfiguredAwsClient; +import io.awspring.cloud.autoconfigure.core.AwsAutoConfiguration; +import io.awspring.cloud.autoconfigure.core.CredentialsProviderAutoConfiguration; +import io.awspring.cloud.autoconfigure.core.RegionProviderAutoConfiguration; +import java.time.Duration; +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; +import software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClient; +import software.amazon.awssdk.services.sqs.SqsAsyncClient; + +/** + * Tests for {@link SqsAsyncClientCustomizer}. + * + * @author Maciej Walkowiak + */ +class SqsAsyncClientCustomizerTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withPropertyValues("spring.cloud.aws.region.static:eu-west-1", + "spring.cloud.aws.credentials.access-key:noop", "spring.cloud.aws.credentials.secret-key:noop") + .withConfiguration(AutoConfigurations.of(AwsAutoConfiguration.class, RegionProviderAutoConfiguration.class, + CredentialsProviderAutoConfiguration.class, SqsAutoConfiguration.class)); + + @Test + void customClientCustomizer() { + contextRunner.withUserConfiguration(CustomizerConfig.class).run(context -> { + ConfiguredAwsClient client = new ConfiguredAwsClient(context.getBean(SqsAsyncClient.class)); + assertThat(client.getApiCallTimeout()).describedAs("sets property from first customizer") + .isEqualTo(Duration.ofMillis(2001)); + assertThat(client.getApiCallAttemptTimeout()).describedAs("sets property from second customizer") + .isEqualTo(Duration.ofMillis(2002)); + assertThat(client.getAsyncHttpClient()).describedAs("sets property from common client customizer") + .isNotNull(); + }); + } + + @Test + void customClientCustomizerWithOrder() { + contextRunner.withUserConfiguration(CustomizerConfigWithOrder.class).run(context -> { + ConfiguredAwsClient client = new ConfiguredAwsClient(context.getBean(SqsAsyncClient.class)); + assertThat(client.getApiCallTimeout()) + .describedAs("property from the customizer with higher order takes precedence") + .isEqualTo(Duration.ofMillis(2001)); + }); + } + + @Configuration(proxyBeanMethods = false) + static class CustomizerConfig { + + @Bean + SqsAsyncClientCustomizer customizer() { + return builder -> { + builder.overrideConfiguration(builder.overrideConfiguration().copy(c -> { + c.apiCallTimeout(Duration.ofMillis(2001)); + })); + }; + } + + @Bean + SqsAsyncClientCustomizer customizer2() { + return builder -> { + builder.overrideConfiguration(builder.overrideConfiguration().copy(c -> { + c.apiCallAttemptTimeout(Duration.ofMillis(2002)); + })); + }; + } + + @Bean + AwsAsyncClientCustomizer awsAsyncClientCustomizer() { + return builder -> { + builder.httpClient( + NettyNioAsyncHttpClient.builder().connectionTimeout(Duration.ofMillis(1542)).build()); + }; + } + } + + @Configuration(proxyBeanMethods = false) + static class CustomizerConfigWithOrder { + + @Bean + @Order(2) + SqsAsyncClientCustomizer customizer() { + return builder -> { + builder.overrideConfiguration(builder.overrideConfiguration().copy(c -> { + c.apiCallTimeout(Duration.ofMillis(2001)); + })); + }; + } + + @Bean + @Order(1) + SqsAsyncClientCustomizer customizer2() { + return builder -> { + builder.overrideConfiguration(builder.overrideConfiguration().copy(c -> { + c.apiCallTimeout(Duration.ofMillis(2000)); + })); + }; + } + } + +} diff --git a/spring-cloud-aws-testcontainers/src/main/java/io/awspring/cloud/testcontainers/AwsClientFactory.java b/spring-cloud-aws-testcontainers/src/main/java/io/awspring/cloud/testcontainers/AwsClientFactory.java index b421ae8ef..5cc0996c3 100644 --- a/spring-cloud-aws-testcontainers/src/main/java/io/awspring/cloud/testcontainers/AwsClientFactory.java +++ b/spring-cloud-aws-testcontainers/src/main/java/io/awspring/cloud/testcontainers/AwsClientFactory.java @@ -24,5 +24,5 @@ * @since 3.2.0 */ public interface AwsClientFactory { - > CLIENT create(BUILDER builder); + > CLIENT create(BUILDER builder); } diff --git a/spring-cloud-aws-testcontainers/src/main/java/io/awspring/cloud/testcontainers/LocalstackAwsClientFactory.java b/spring-cloud-aws-testcontainers/src/main/java/io/awspring/cloud/testcontainers/LocalstackAwsClientFactory.java index bb95368c6..40f4f0173 100644 --- a/spring-cloud-aws-testcontainers/src/main/java/io/awspring/cloud/testcontainers/LocalstackAwsClientFactory.java +++ b/spring-cloud-aws-testcontainers/src/main/java/io/awspring/cloud/testcontainers/LocalstackAwsClientFactory.java @@ -38,7 +38,7 @@ public LocalstackAwsClientFactory(LocalStackContainer localstack) { } @Override - public > CLIENT create(BUILDER builder) { + public > CLIENT create(BUILDER builder) { return configurer.configure(builder).build(); }