Skip to content

Commit 4aeda8e

Browse files
authored
Expose client-level context params on service client configuration (#4834)
* Expose client-level context params on service client configuration * Expose client-level context params on service client configuration * Added changelog * Reverted unnecessary change to AttributeMap * Modified changelog
1 parent e222084 commit 4aeda8e

File tree

12 files changed

+236
-28
lines changed

12 files changed

+236
-28
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"type": "feature",
3+
"category": "AWS SDK for Java v2",
4+
"contributor": "anirudh9391",
5+
"description": "Allowing SDK plugins to read and modify S3's crossRegionEnabled and SQS's checksumValidationEnabled"
6+
}

codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/AsyncClientBuilderClass.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ private MethodSpec buildClientMethod() {
138138
builder.addStatement("$1T client = new $2T(clientConfiguration)",
139139
clientInterfaceName, clientClassName);
140140
if (model.asyncClientDecoratorClassName().isPresent()) {
141-
builder.addStatement("return new $T().decorate(client, clientConfiguration, clientContextParams.copy().build())",
141+
builder.addStatement("return new $T().decorate(client, clientConfiguration)",
142142
PoetUtils.classNameFromFqcn(model.asyncClientDecoratorClassName().get()));
143143
} else {
144144
builder.addStatement("return client");

codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/SyncClientBuilderClass.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ private MethodSpec buildClientMethod() {
130130
builder.addStatement("$1T client = new $2T(clientConfiguration)",
131131
clientInterfaceName, clientClassName);
132132
if (model.syncClientDecoratorClassName().isPresent()) {
133-
builder.addStatement("return new $T().decorate(client, clientConfiguration, clientContextParams.copy().build())",
133+
builder.addStatement("return new $T().decorate(client, clientConfiguration)",
134134
PoetUtils.classNameFromFqcn(model.syncClientDecoratorClassName().get()));
135135
} else {
136136
builder.addStatement("return client");

codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/SyncClientClass.java

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434
import java.util.ArrayList;
3535
import java.util.Collections;
3636
import java.util.List;
37+
import java.util.Map;
38+
import java.util.Objects;
3739
import java.util.concurrent.CompletableFuture;
3840
import java.util.stream.Collectors;
3941
import java.util.stream.Stream;
@@ -47,6 +49,7 @@
4749
import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel;
4850
import software.amazon.awssdk.codegen.model.intermediate.OperationModel;
4951
import software.amazon.awssdk.codegen.model.intermediate.Protocol;
52+
import software.amazon.awssdk.codegen.model.service.ClientContextParam;
5053
import software.amazon.awssdk.codegen.poet.PoetExtension;
5154
import software.amazon.awssdk.codegen.poet.PoetUtils;
5255
import software.amazon.awssdk.codegen.poet.auth.scheme.AuthSchemeSpecUtils;
@@ -56,6 +59,7 @@
5659
import software.amazon.awssdk.codegen.poet.client.specs.QueryProtocolSpec;
5760
import software.amazon.awssdk.codegen.poet.client.specs.XmlProtocolSpec;
5861
import software.amazon.awssdk.codegen.poet.model.ServiceClientConfigurationUtils;
62+
import software.amazon.awssdk.codegen.poet.rules.EndpointRulesSpecUtils;
5963
import software.amazon.awssdk.core.RequestOverrideConfiguration;
6064
import software.amazon.awssdk.core.SdkPlugin;
6165
import software.amazon.awssdk.core.SdkRequest;
@@ -69,8 +73,11 @@
6973
import software.amazon.awssdk.metrics.MetricCollector;
7074
import software.amazon.awssdk.metrics.MetricPublisher;
7175
import software.amazon.awssdk.metrics.NoOpMetricCollector;
76+
import software.amazon.awssdk.utils.AttributeMap;
77+
import software.amazon.awssdk.utils.CollectionUtils;
7278
import software.amazon.awssdk.utils.CompletableFutureUtils;
7379
import software.amazon.awssdk.utils.Logger;
80+
import software.amazon.awssdk.utils.Validate;
7481

7582
public class SyncClientClass extends SyncClientInterface {
7683

@@ -418,7 +425,7 @@ protected MethodSpec.Builder waiterOperationBody(MethodSpec.Builder builder) {
418425
poetExtensions.getSyncWaiterInterface());
419426
}
420427

421-
protected static MethodSpec updateSdkClientConfigurationMethod(
428+
protected MethodSpec updateSdkClientConfigurationMethod(
422429
TypeName serviceClientConfigurationBuilderClassName,
423430
boolean shouldAddClientReference) {
424431
MethodSpec.Builder builder = MethodSpec.methodBuilder("updateSdkClientConfiguration")
@@ -442,9 +449,34 @@ protected static MethodSpec updateSdkClientConfigurationMethod(
442449
.addStatement("$1T serviceConfigBuilder = new $1T(configuration)", serviceClientConfigurationBuilderClassName)
443450
.beginControlFlow("for ($T plugin : plugins)", SdkPlugin.class)
444451
.addStatement("plugin.configureClient(serviceConfigBuilder)")
445-
.endControlFlow()
446-
.addStatement("return configuration.build()");
452+
.endControlFlow();
453+
EndpointRulesSpecUtils endpointRulesSpecUtils = new EndpointRulesSpecUtils(this.model);
454+
455+
if (model.getCustomizationConfig() == null ||
456+
CollectionUtils.isNullOrEmpty(model.getCustomizationConfig().getCustomClientContextParams())) {
457+
builder.addStatement("return configuration.build()");
458+
return builder.build();
459+
}
460+
461+
Map<String, ClientContextParam> customClientConfigParams = model.getCustomizationConfig().getCustomClientContextParams();
462+
463+
builder.addCode("$1T newContextParams = configuration.option($2T.CLIENT_CONTEXT_PARAMS);\n"
464+
+ "$1T originalContextParams = clientConfiguration.option($2T.CLIENT_CONTEXT_PARAMS);",
465+
AttributeMap.class, SdkClientOption.class);
466+
467+
builder.addCode("newContextParams = (newContextParams != null) ? newContextParams : $1T.empty();\n"
468+
+ "originalContextParams = originalContextParams != null ? originalContextParams : $1T.empty();",
469+
AttributeMap.class);
470+
471+
customClientConfigParams.forEach((n, m) -> {
472+
String keyName = model.getNamingStrategy().getEnumValueName(n);
473+
builder.addStatement("$1T.validState($2T.equals(originalContextParams.get($3T.$4N), newContextParams.get($3T.$4N)),"
474+
+ " $5S)",
475+
Validate.class, Objects.class, endpointRulesSpecUtils.clientContextParamsName(), keyName,
476+
keyName + " cannot be modified by request level plugins");
477+
});
447478

479+
builder.addStatement("return configuration.build()");
448480
return builder.build();
449481
}
450482
}

codegen/src/main/java/software/amazon/awssdk/codegen/poet/model/ServiceClientConfigurationUtils.java

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import com.squareup.javapoet.TypeName;
2323
import com.squareup.javapoet.WildcardTypeName;
2424
import java.net.URI;
25+
import java.util.ArrayList;
2526
import java.util.Arrays;
2627
import java.util.Collections;
2728
import java.util.HashMap;
@@ -31,6 +32,7 @@
3132
import software.amazon.awssdk.awscore.client.config.AwsClientOption;
3233
import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel;
3334
import software.amazon.awssdk.codegen.poet.auth.scheme.AuthSchemeSpecUtils;
35+
import software.amazon.awssdk.codegen.poet.rules.EndpointRulesSpecUtils;
3436
import software.amazon.awssdk.core.client.config.ClientOption;
3537
import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration;
3638
import software.amazon.awssdk.core.client.config.SdkClientConfiguration;
@@ -41,12 +43,14 @@
4143
import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity;
4244
import software.amazon.awssdk.identity.spi.IdentityProvider;
4345
import software.amazon.awssdk.regions.Region;
46+
import software.amazon.awssdk.utils.AttributeMap;
4447
import software.amazon.awssdk.utils.Validate;
4548

4649
public class ServiceClientConfigurationUtils {
4750
private final AuthSchemeSpecUtils authSchemeSpecUtils;
4851
private final ClassName configurationClassName;
4952
private final ClassName configurationBuilderClassName;
53+
private final EndpointRulesSpecUtils endpointRulesSpecUtils;
5054
private final List<Field> fields;
5155

5256
public ServiceClientConfigurationUtils(IntermediateModel model) {
@@ -56,7 +60,8 @@ public ServiceClientConfigurationUtils(IntermediateModel model) {
5660
configurationBuilderClassName = ClassName.get(model.getMetadata().getFullClientInternalPackageName(),
5761
serviceId + "ServiceClientConfigurationBuilder");
5862
authSchemeSpecUtils = new AuthSchemeSpecUtils(model);
59-
fields = fields();
63+
endpointRulesSpecUtils = new EndpointRulesSpecUtils(model);
64+
fields = fields(model);
6065
}
6166

6267
/**
@@ -81,16 +86,44 @@ public List<Field> serviceClientConfigurationFields() {
8186
return Collections.unmodifiableList(fields);
8287
}
8388

84-
private List<Field> fields() {
85-
return Arrays.asList(
89+
private List<Field> fields(IntermediateModel model) {
90+
List<Field> fields = new ArrayList<>();
91+
92+
fields.addAll(Arrays.asList(
8693
overrideConfigurationField(),
8794
endpointOverrideField(),
8895
endpointProviderField(),
8996
regionField(),
9097
credentialsProviderField(),
9198
authSchemesField(),
9299
authSchemeProviderField()
93-
);
100+
));
101+
fields.addAll(addCustomClientParams(model));
102+
return fields;
103+
}
104+
105+
private List<Field> addCustomClientParams(IntermediateModel model) {
106+
List<Field> customClientParamFields = new ArrayList<>();
107+
108+
if (model.getCustomizationConfig() != null && model.getCustomizationConfig().getCustomClientContextParams() != null) {
109+
model.getCustomizationConfig().getCustomClientContextParams().forEach((n, m) -> {
110+
111+
String paramName = endpointRulesSpecUtils.paramMethodName(n);
112+
String keyName = model.getNamingStrategy().getEnumValueName(n);
113+
TypeName type = endpointRulesSpecUtils.toJavaType(m.getType());
114+
115+
customClientParamFields.add(fieldBuilder(paramName, type)
116+
.doc(m.getDocumentation())
117+
.isInherited(false)
118+
.localSetter(basicLocalSetterCode(paramName))
119+
.localGetter(basicLocalGetterCode(paramName))
120+
.configSetter(customClientConfigParamSetter(paramName, keyName))
121+
.configGetter(customClientConfigParamGetter(keyName))
122+
.build());
123+
});
124+
}
125+
126+
return customClientParamFields;
94127
}
95128

96129
private Field overrideConfigurationField() {
@@ -268,6 +301,27 @@ private CodeBlock authSchemeProviderConfigGetter() {
268301
.build();
269302
}
270303

304+
private CodeBlock customClientConfigParamSetter(String parameterName, String keyName) {
305+
return CodeBlock.builder()
306+
.addStatement("config.option($1T.CLIENT_CONTEXT_PARAMS, "
307+
+ "config.computeOptionIfAbsent($1T.CLIENT_CONTEXT_PARAMS, $2T::empty)"
308+
+ ".toBuilder().put($3T.$4N, $5N).build())",
309+
SdkClientOption.class,
310+
AttributeMap.class,
311+
endpointRulesSpecUtils.clientContextParamsName(),
312+
keyName, parameterName)
313+
.addStatement("return this")
314+
.build();
315+
}
316+
317+
private CodeBlock customClientConfigParamGetter(String keyName) {
318+
return CodeBlock.builder()
319+
.addStatement("return config.computeOptionIfAbsent($T.CLIENT_CONTEXT_PARAMS, $T::empty)\n"
320+
+ ".get($T.$N)", SdkClientOption.class, AttributeMap.class,
321+
endpointRulesSpecUtils.clientContextParamsName(), keyName)
322+
.build();
323+
}
324+
271325
private CodeBlock basicLocalSetterCode(String fieldName) {
272326
return CodeBlock.builder()
273327
.addStatement("this.$1N = $1N", fieldName)

codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-composed-async-client-builder-class.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,6 @@ protected final JsonAsyncClient buildClient() {
3434
SdkClientConfiguration clientConfiguration = super.asyncClientConfiguration();
3535
this.validateClientOptions(clientConfiguration);
3636
JsonAsyncClient client = new DefaultJsonAsyncClient(clientConfiguration);
37-
return new AsyncClientDecorator().decorate(client, clientConfiguration, clientContextParams.copy().build());
37+
return new AsyncClientDecorator().decorate(client, clientConfiguration);
3838
}
3939
}

codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-composed-sync-client-builder-class.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,6 @@ protected final JsonClient buildClient() {
3434
SdkClientConfiguration clientConfiguration = super.syncClientConfiguration();
3535
this.validateClientOptions(clientConfiguration);
3636
JsonClient client = new DefaultJsonClient(clientConfiguration);
37-
return new SyncClientDecorator().decorate(client, clientConfiguration, clientContextParams.copy().build());
37+
return new SyncClientDecorator().decorate(client, clientConfiguration);
3838
}
3939
}

services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/client/S3AsyncClientDecorator.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.util.function.Predicate;
2121
import software.amazon.awssdk.annotations.SdkInternalApi;
2222
import software.amazon.awssdk.core.client.config.SdkClientConfiguration;
23+
import software.amazon.awssdk.core.client.config.SdkClientOption;
2324
import software.amazon.awssdk.services.s3.S3AsyncClient;
2425
import software.amazon.awssdk.services.s3.endpoints.S3ClientContextParams;
2526
import software.amazon.awssdk.services.s3.internal.crossregion.S3CrossRegionAsyncClient;
@@ -39,8 +40,8 @@ public S3AsyncClientDecorator() {
3940
}
4041

4142
public S3AsyncClient decorate(S3AsyncClient base,
42-
SdkClientConfiguration clientConfiguration,
43-
AttributeMap clientContextParams) {
43+
SdkClientConfiguration clientConfiguration) {
44+
AttributeMap clientContextParams = clientConfiguration.option(SdkClientOption.CLIENT_CONTEXT_PARAMS);
4445
List<ConditionalDecorator<S3AsyncClient>> decorators = new ArrayList<>();
4546
decorators.add(ConditionalDecorator.create(
4647
isCrossRegionEnabledAsync(clientContextParams),

services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/client/S3SyncClientDecorator.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.util.function.Predicate;
2121
import software.amazon.awssdk.annotations.SdkInternalApi;
2222
import software.amazon.awssdk.core.client.config.SdkClientConfiguration;
23+
import software.amazon.awssdk.core.client.config.SdkClientOption;
2324
import software.amazon.awssdk.services.s3.S3Client;
2425
import software.amazon.awssdk.services.s3.endpoints.S3ClientContextParams;
2526
import software.amazon.awssdk.services.s3.internal.crossregion.S3CrossRegionSyncClient;
@@ -33,8 +34,8 @@ public S3SyncClientDecorator() {
3334
}
3435

3536
public S3Client decorate(S3Client base,
36-
SdkClientConfiguration clientConfiguration,
37-
AttributeMap clientContextParams) {
37+
SdkClientConfiguration clientConfiguration) {
38+
AttributeMap clientContextParams = clientConfiguration.option(SdkClientOption.CLIENT_CONTEXT_PARAMS);
3839
List<ConditionalDecorator<S3Client>> decorators = new ArrayList<>();
3940
decorators.add(ConditionalDecorator.create(isCrossRegionEnabledSync(clientContextParams),
4041
S3CrossRegionSyncClient::new));

services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/ClientDecorationFactoryTest.java

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
import org.junit.jupiter.params.ParameterizedTest;
2323
import org.junit.jupiter.params.provider.Arguments;
2424
import org.junit.jupiter.params.provider.MethodSource;
25+
import software.amazon.awssdk.core.client.config.ClientOption;
26+
import software.amazon.awssdk.core.client.config.SdkClientConfiguration;
27+
import software.amazon.awssdk.core.client.config.SdkClientOption;
2528
import software.amazon.awssdk.services.s3.S3AsyncClient;
2629
import software.amazon.awssdk.services.s3.S3Client;
2730
import software.amazon.awssdk.services.s3.endpoints.S3ClientContextParams;
@@ -33,14 +36,13 @@
3336

3437
public class ClientDecorationFactoryTest {
3538

36-
AttributeMap.Builder clientContextParams = AttributeMap.builder();
39+
static SdkClientConfiguration.Builder clientConfiguration = SdkClientConfiguration.builder();
3740

3841
@ParameterizedTest
3942
@MethodSource("syncTestCases")
40-
41-
void syncClientTest(AttributeMap clientContextParams, Class<Object> clazz, boolean isClass) {
43+
void syncClientTest(SdkClientConfiguration clientConfiguration, Class<Object> clazz, boolean isClass) {
4244
S3SyncClientDecorator decorator = new S3SyncClientDecorator();
43-
S3Client decorateClient = decorator.decorate(S3Client.create(), null, clientContextParams);
45+
S3Client decorateClient = decorator.decorate(S3Client.create(), clientConfiguration);
4446
if (isClass) {
4547
assertThat(decorateClient).isInstanceOf(clazz);
4648
} else {
@@ -50,10 +52,10 @@ void syncClientTest(AttributeMap clientContextParams, Class<Object> clazz, boole
5052

5153
@ParameterizedTest
5254
@MethodSource("asyncTestCases")
53-
void asyncClientTest(AttributeMap clientContextParams, Class<Object> clazz, boolean isClass) {
55+
void asyncClientTest(SdkClientConfiguration clientConfiguration, Class<Object> clazz, boolean isClass) {
5456
S3AsyncClientDecorator decorator = new S3AsyncClientDecorator();
5557
S3AsyncClient decoratedClient = decorator.decorate(S3AsyncClient.create(),
56-
null ,clientContextParams);
58+
clientConfiguration);
5759
if (isClass) {
5860
assertThat(decoratedClient).isInstanceOf(clazz);
5961
} else {
@@ -64,24 +66,28 @@ void asyncClientTest(AttributeMap clientContextParams, Class<Object> clazz, bool
6466

6567
private static Stream<Arguments> syncTestCases() {
6668
return Stream.of(
67-
Arguments.of(AttributeMap.builder().build(), S3CrossRegionSyncClient.class, false),
68-
Arguments.of(AttributeMap.builder().put(S3ClientContextParams.CROSS_REGION_ACCESS_ENABLED, false).build(),
69+
Arguments.of(clientConfiguration.option(SdkClientOption.CLIENT_CONTEXT_PARAMS, AttributeMap.builder().build()).build(), S3CrossRegionSyncClient.class, false),
70+
Arguments.of(clientConfiguration.option(SdkClientOption.CLIENT_CONTEXT_PARAMS,
71+
AttributeMap.builder().put(S3ClientContextParams.CROSS_REGION_ACCESS_ENABLED, false).build()).build(),
6972
S3CrossRegionSyncClient.class, false),
70-
Arguments.of(AttributeMap.builder().put(S3ClientContextParams.CROSS_REGION_ACCESS_ENABLED, true).build(),
73+
Arguments.of(clientConfiguration.option(SdkClientOption.CLIENT_CONTEXT_PARAMS,
74+
AttributeMap.builder().put(S3ClientContextParams.CROSS_REGION_ACCESS_ENABLED, true).build()).build(),
7175
S3CrossRegionSyncClient.class, true)
7276
);
7377
}
7478

7579
private static Stream<Arguments> asyncTestCases() {
7680
return Stream.of(
77-
Arguments.of(AttributeMap.builder().build(),
81+
Arguments.of(clientConfiguration.option(SdkClientOption.CLIENT_CONTEXT_PARAMS, AttributeMap.builder().build()).build(),
7882
S3CrossRegionAsyncClient.class,
7983
false),
80-
Arguments.of(AttributeMap.builder().put(S3ClientContextParams.CROSS_REGION_ACCESS_ENABLED, false).build(),
84+
Arguments.of(clientConfiguration.option(SdkClientOption.CLIENT_CONTEXT_PARAMS,
85+
AttributeMap.builder().put(S3ClientContextParams.CROSS_REGION_ACCESS_ENABLED, false).build()).build(),
8186
S3CrossRegionAsyncClient.class,
8287
false),
83-
Arguments.of(AttributeMap.builder().put(S3ClientContextParams.CROSS_REGION_ACCESS_ENABLED, true).build()
84-
, S3CrossRegionAsyncClient.class,
88+
Arguments.of(clientConfiguration.option(SdkClientOption.CLIENT_CONTEXT_PARAMS,
89+
AttributeMap.builder().put(S3ClientContextParams.CROSS_REGION_ACCESS_ENABLED, true).build()).build(),
90+
S3CrossRegionAsyncClient.class,
8591
true)
8692
);
8793
}

0 commit comments

Comments
 (0)