Skip to content

URI Cache for DynamoDB Account Id based Endpoints #6244

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jul 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions .changes/next-release/feature-AWSSDKforJavav2-960b6f3.json

This file was deleted.

6 changes: 6 additions & 0 deletions .changes/next-release/feature-AWSSDKforJavav2-b405876.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"type": "feature",
"category": "Amazon DynamoDB",
"contributor": "",
"description": "Enable caching results to URI constructors for account-id based endpoints"
}
6 changes: 6 additions & 0 deletions .changes/next-release/feature-AWSSDKforJavav2-f6d5c46.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"type": "feature",
"category": "AWS SDK for Java v2",
"contributor": "",
"description": "Add support for caching results to URI constructors for account-id based endpoints"
}
Original file line number Diff line number Diff line change
Expand Up @@ -353,10 +353,14 @@
</Or>
<Bug pattern="ASYNC_BLOCKING_CALL"/>
</Match>

<!-- False positive -->
<Match>
<Class name="software.amazon.awssdk.v2migration.EnumCasingToV2$Visitor"/>
<Bug pattern="NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE"/>
</Match>
<Match>
<Class name="software.amazon.awssdk.utils.uri.SdkUri" />
<Bug pattern="BC_UNCONFIRMED_CAST_OF_RETURN_VALUE" />
</Match>
</FindBugsFilter>
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,11 @@ public class CustomizationConfig {
*/
private boolean enableEnvironmentBearerToken = false;

/**
* A boolean flag to indicate if the code-generated endpoint providers class should cache the calls to URI constructors.
*/
private boolean enableEndpointProviderUriCaching;

private CustomizationConfig() {
}

Expand Down Expand Up @@ -939,4 +944,12 @@ public boolean isEnableEnvironmentBearerToken() {
public void setEnableEnvironmentBearerToken(boolean enableEnvironmentBearerToken) {
this.enableEnvironmentBearerToken = enableEnvironmentBearerToken;
}

public boolean getEnableEndpointProviderUriCaching() {
return enableEndpointProviderUriCaching;
}

public void setEnableEndpointProviderUriCaching(boolean enableEndpointProviderUriCaching) {
this.enableEndpointProviderUriCaching = enableEndpointProviderUriCaching;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import software.amazon.awssdk.awscore.endpoints.authscheme.SigV4aAuthScheme;
import software.amazon.awssdk.codegen.model.config.customization.KeyTypePair;
import software.amazon.awssdk.endpoints.Endpoint;
import software.amazon.awssdk.utils.uri.SdkUri;

public class CodeGeneratorVisitor extends WalkRuleExpressionVisitor {
private static final Logger log = LoggerFactory.getLogger(CodeGeneratorVisitor.class);
Expand All @@ -38,17 +39,20 @@ public class CodeGeneratorVisitor extends WalkRuleExpressionVisitor {
private final SymbolTable symbolTable;
private final Map<String, KeyTypePair> knownEndpointAttributes;
private final Map<String, ComputeScopeTree.Scope> ruleIdToScope;
private final boolean endpointCaching;

public CodeGeneratorVisitor(RuleRuntimeTypeMirror typeMirror,
SymbolTable symbolTable,
Map<String, KeyTypePair> knownEndpointAttributes,
Map<String, ComputeScopeTree.Scope> ruleIdToScope,
boolean endpointCaching,
CodeBlock.Builder builder) {
this.builder = builder;
this.symbolTable = symbolTable;
this.knownEndpointAttributes = knownEndpointAttributes;
this.ruleIdToScope = ruleIdToScope;
this.typeMirror = typeMirror;
this.endpointCaching = endpointCaching;
}

@Override
Expand Down Expand Up @@ -329,7 +333,11 @@ private String callParams(String ruleId) {
@Override
public Void visitEndpointExpression(EndpointExpression e) {
builder.add("return $T.endpoint(", typeMirror.rulesResult().type());
builder.add("$T.builder().url($T.create(", Endpoint.class, URI.class);
if (endpointCaching) {
builder.add("$T.builder().url($T.getInstance().create(", Endpoint.class, SdkUri.class);
} else {
builder.add("$T.builder().url($T.create(", Endpoint.class, URI.class);
}
e.url().accept(this);
builder.add("))");
e.headers().accept(this);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,10 +229,12 @@ private MethodSpec.Builder methodBuilderForRule(RuleSetExpression expr) {
}

private void codegenExpr(RuleSetExpression expr, CodeBlock.Builder builder) {
boolean useEndpointCaching = intermediateModel.getCustomizationConfig().getEnableEndpointProviderUriCaching();
CodeGeneratorVisitor visitor = new CodeGeneratorVisitor(typeMirror,
utils.symbolTable(),
knownEndpointAttributes,
utils.scopesByName(),
useEndpointCaching,
builder);
visitor.visitRuleSetExpression(expr);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@ public static IntermediateModel awsJsonServiceModels() {
File customizationModel = new File(ClientTestModels.class.getResource("client/c2j/json/customization.config").getFile());
File paginatorsModel = new File(ClientTestModels.class.getResource("client/c2j/json/paginators.json").getFile());
C2jModels models = C2jModels.builder()
.serviceModel(getServiceModel(serviceModel))
.customizationConfig(getCustomizationConfig(customizationModel))
.paginatorsModel(getPaginatorsModel(paginatorsModel))
.build();
.serviceModel(getServiceModel(serviceModel))
.customizationConfig(getCustomizationConfig(customizationModel))
.paginatorsModel(getPaginatorsModel(paginatorsModel))
.build();

return new IntermediateModelBuilder(models).build();
}
Expand Down Expand Up @@ -136,13 +136,13 @@ public static IntermediateModel queryServiceModels() {
new File(ClientTestModels.class.getResource("client/c2j/query/endpoint-tests.json").getFile());

C2jModels models = C2jModels
.builder()
.serviceModel(getServiceModel(serviceModel))
.customizationConfig(getCustomizationConfig(customizationModel))
.waitersModel(getWaiters(waitersModel))
.builder()
.serviceModel(getServiceModel(serviceModel))
.customizationConfig(getCustomizationConfig(customizationModel))
.waitersModel(getWaiters(waitersModel))
.endpointRuleSetModel(getEndpointRuleSet(endpointRuleSetModel))
.endpointTestSuiteModel(getEndpointTestSuite(endpointTestsModel))
.build();
.build();

return new IntermediateModelBuilder(models).build();
}
Expand Down Expand Up @@ -206,6 +206,28 @@ public static IntermediateModel queryServiceModelsWithUnknownEndpointProperties(
return new IntermediateModelBuilder(models).build();
}

public static IntermediateModel queryServiceModelsWithUriCache() {
File serviceModel = new File(ClientTestModels.class.getResource("client/c2j/query/service-2.json").getFile());
File customizationModel =
new File(ClientTestModels.class.getResource("client/c2j/query/customization-uri-cache.config").getFile());
File waitersModel = new File(ClientTestModels.class.getResource("client/c2j/query/waiters-2.json").getFile());
File endpointRuleSetModel =
new File(ClientTestModels.class.getResource("client/c2j/query/endpoint-rule-set.json").getFile());
File endpointTestsModel =
new File(ClientTestModels.class.getResource("client/c2j/query/endpoint-tests.json").getFile());

C2jModels models = C2jModels
.builder()
.serviceModel(getServiceModel(serviceModel))
.customizationConfig(getCustomizationConfig(customizationModel))
.waitersModel(getWaiters(waitersModel))
.endpointRuleSetModel(getEndpointRuleSet(endpointRuleSetModel))
.endpointTestSuiteModel(getEndpointTestSuite(endpointTestsModel))
.build();

return new IntermediateModelBuilder(models).build();
}

public static IntermediateModel queryServiceModelsEndpointAuthParamsWithAllowList() {
File serviceModel = new File(ClientTestModels.class.getResource("client/c2j/query/service-2.json").getFile());
File customizationModel =
Expand Down Expand Up @@ -458,9 +480,9 @@ public static IntermediateModel customContentTypeModels() {
File customizationModel = new File(ClientTestModels.class.getResource("client/c2j/customservicemetadata/customization.config").getFile());

C2jModels models = C2jModels.builder()
.serviceModel(getServiceModel(serviceModel))
.customizationConfig(getCustomizationConfig(customizationModel))
.build();
.serviceModel(getServiceModel(serviceModel))
.customizationConfig(getCustomizationConfig(customizationModel))
.build();

return new IntermediateModelBuilder(models).build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,11 @@ void unknownEndpointProperties() {
new EndpointProviderSpec2(ClientTestModels.queryServiceModelsWithUnknownEndpointProperties());
assertThat(endpointProviderSpec, generatesTo("endpoint-provider-unknown-property-class.java"));
}

@Test
void endpointProviderClassWithUriCache() {
ClassSpec endpointProviderSpec =
new EndpointProviderSpec2(ClientTestModels.queryServiceModelsWithUriCache());
assertThat(endpointProviderSpec, generatesTo("endpoint-provider-uri-cache-class.java"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"authPolicyActions" : {
"skip" : true
},
"skipEndpointTests": {
"test case 4": "Does not work"
},
"endpointParameters": {
"CustomEndpointArray": {
"required": false,
"documentation": "Parameter from the customization config",
"type": "StringArray"
},
"ArnList": {
"required": false,
"documentation": "Parameter from the customization config",
"type": "StringArray"
}
},
"customOperationContextParams": [
{
"operationName": "OperationWithCustomizedOperationContextParam",
"operationContextParamsMap": {
"customEndpointArray": {
"path": "ListMember.StringList[*].LeafString"
}
}
}
],
"preClientExecutionRequestCustomizer": {
"OperationWithCustomMember": {
"methodName": "dummyRequestModifier",
"className": "software.amazon.awssdk.codegen.internal.UtilsTest"
}
},
"enableEndpointProviderUriCaching": true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package software.amazon.awssdk.services.query.endpoints.internal;

import java.util.Arrays;
import java.util.concurrent.CompletableFuture;
import software.amazon.awssdk.annotations.Generated;
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.awscore.endpoints.AwsEndpointAttribute;
import software.amazon.awssdk.awscore.endpoints.authscheme.SigV4AuthScheme;
import software.amazon.awssdk.awscore.endpoints.authscheme.SigV4aAuthScheme;
import software.amazon.awssdk.core.exception.SdkClientException;
import software.amazon.awssdk.endpoints.Endpoint;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.query.endpoints.QueryEndpointParams;
import software.amazon.awssdk.services.query.endpoints.QueryEndpointProvider;
import software.amazon.awssdk.utils.CompletableFutureUtils;
import software.amazon.awssdk.utils.Validate;
import software.amazon.awssdk.utils.uri.SdkUri;

@Generated("software.amazon.awssdk:codegen")
@SdkInternalApi
public final class DefaultQueryEndpointProvider implements QueryEndpointProvider {
@Override
public CompletableFuture<Endpoint> resolveEndpoint(QueryEndpointParams params) {
Validate.notNull(params.region(), "Parameter 'region' must not be null");
try {
Region region = params.region();
String regionId = region == null ? null : region.id();
RuleResult result = endpointRule0(params, regionId);
if (result.canContinue()) {
throw SdkClientException.create("Rule engine did not reach an error or endpoint result");
}
if (result.isError()) {
String errorMsg = result.error();
if (errorMsg.contains("Invalid ARN") && errorMsg.contains(":s3:::")) {
errorMsg += ". Use the bucket name instead of simple bucket ARNs in GetBucketLocationRequest.";
}
throw SdkClientException.create(errorMsg);
}
return CompletableFuture.completedFuture(result.endpoint());
} catch (Exception error) {
return CompletableFutureUtils.failedFuture(error);
}
}

private static RuleResult endpointRule0(QueryEndpointParams params, String region) {
return endpointRule1(params, region);
}

private static RuleResult endpointRule1(QueryEndpointParams params, String region) {
RulePartition partitionResult = RulesFunctions.awsPartition(region);
if (partitionResult != null) {
RuleResult result = endpointRule2(params, partitionResult);
if (result.isResolved()) {
return result;
}
result = endpointRule6(params, region, partitionResult);
if (result.isResolved()) {
return result;
}
return RuleResult.error(region + " is not a valid HTTP host-label");
if (params.useFipsEndpoint() == null && params.useDualStackEndpoint() != null && params.useDualStackEndpoint()
&& params.arnList() != null) {
String firstArn = RulesFunctions.listAccess(params.arnList(), 0);
if (firstArn != null) {
RuleArn parsedArn = RulesFunctions.awsParseArn(firstArn);
if (parsedArn != null) {
return RuleResult.endpoint(Endpoint
.builder()
.url(SdkUri.getInstance().create(
"https://" + params.endpointId() + ".query." + partitionResult.dualStackDnsSuffix()))
.putAttribute(
AwsEndpointAttribute.AUTH_SCHEMES,
Arrays.asList(SigV4aAuthScheme.builder().signingName("query")
.signingRegionSet(Arrays.asList("*")).build())).build());
}
}
}
}
return RuleResult.carryOn();
}

private static RuleResult endpointRule2(QueryEndpointParams params, RulePartition partitionResult) {
if (params.endpointId() != null) {
if (params.useFipsEndpoint() != null && params.useFipsEndpoint()) {
return RuleResult.error("FIPS endpoints not supported with multi-region endpoints");
}
if (params.useFipsEndpoint() == null && params.useDualStackEndpoint() != null && params.useDualStackEndpoint()) {
return RuleResult.endpoint(Endpoint
.builder()
.url(SdkUri.getInstance().create(
"https://" + params.endpointId() + ".query." + partitionResult.dualStackDnsSuffix()))
.putAttribute(
AwsEndpointAttribute.AUTH_SCHEMES,
Arrays.asList(SigV4aAuthScheme.builder().signingName("query")
.signingRegionSet(Arrays.asList("*")).build())).build());
}
return RuleResult.endpoint(Endpoint
.builder()
.url(SdkUri.getInstance().create("https://" + params.endpointId() + ".query." + partitionResult.dnsSuffix()))
.putAttribute(
AwsEndpointAttribute.AUTH_SCHEMES,
Arrays.asList(SigV4aAuthScheme.builder().signingName("query").signingRegionSet(Arrays.asList("*"))
.build())).build());
}
return RuleResult.carryOn();
}

private static RuleResult endpointRule6(QueryEndpointParams params, String region, RulePartition partitionResult) {
if (RulesFunctions.isValidHostLabel(region, false)) {
if (params.useFipsEndpoint() != null && params.useFipsEndpoint() && params.useDualStackEndpoint() == null) {
return RuleResult.endpoint(Endpoint
.builder()
.url(SdkUri.getInstance().create("https://query-fips." + region + "." + partitionResult.dnsSuffix()))
.putAttribute(
AwsEndpointAttribute.AUTH_SCHEMES,
Arrays.asList(SigV4aAuthScheme.builder().signingName("query")
.signingRegionSet(Arrays.asList("*")).build())).build());
}
if (params.useDualStackEndpoint() != null && params.useDualStackEndpoint() && params.useFipsEndpoint() == null) {
return RuleResult.endpoint(Endpoint
.builder()
.url(SdkUri.getInstance().create("https://query." + region + "." + partitionResult.dualStackDnsSuffix()))
.putAttribute(
AwsEndpointAttribute.AUTH_SCHEMES,
Arrays.asList(SigV4aAuthScheme.builder().signingName("query")
.signingRegionSet(Arrays.asList("*")).build(),
SigV4AuthScheme.builder().signingName("query").signingRegion(region).build())).build());
}
if (params.useDualStackEndpoint() != null && params.useFipsEndpoint() != null && params.useDualStackEndpoint()
&& params.useFipsEndpoint()) {
return RuleResult.endpoint(Endpoint
.builder()
.url(SdkUri.getInstance().create(
"https://query-fips." + region + "." + partitionResult.dualStackDnsSuffix()))
.putAttribute(
AwsEndpointAttribute.AUTH_SCHEMES,
Arrays.asList(SigV4aAuthScheme.builder().signingName("query")
.signingRegionSet(Arrays.asList("*")).build())).build());
}
return RuleResult.endpoint(Endpoint.builder()
.url(SdkUri.getInstance().create("https://query." + region + "." + partitionResult.dnsSuffix())).build());
}
return RuleResult.carryOn();
}

@Override
public boolean equals(Object rhs) {
return rhs != null && getClass().equals(rhs.getClass());
}

@Override
public int hashCode() {
return getClass().hashCode();
}
}
Loading
Loading