Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,61 @@
package software.amazon.awssdk.codegen.poet.crac;

import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import javax.lang.model.element.Modifier;
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel;
import software.amazon.awssdk.codegen.model.intermediate.Protocol;
import software.amazon.awssdk.codegen.poet.ClassSpec;
import software.amazon.awssdk.codegen.poet.PoetExtension;
import software.amazon.awssdk.codegen.poet.PoetUtils;
import software.amazon.awssdk.core.crac.SdkWarmUpProvider;

/**
* Generates an empty {@link SdkWarmUpProvider} implementation per service. The {@code warmUp()} body is intentionally a
* no-op in this stage; the synthetic priming call is added in a later stage. ServiceLoader requires a public no-arg
* constructor, which the default constructor satisfies.
* Generates an {@link SdkWarmUpProvider} implementation per service.
*
* <p>The {@code warmUp()} body instantiates and closes a synthetic sync client (when the service generates one) and a
* synthetic async client, each wired to an in-memory canned HTTP client, dummy credentials, a fixed region and a local
* {@code endpointOverride}. Building the clients JIT-compiles the client construction and configuration-resolution path
* before a CRaC checkpoint. The operation call that exercises the marshal/unmarshal pipeline is added in a later stage.
*/
public class WarmUpProviderSpec implements ClassSpec {

private static final String CANNED_RESPONSE_FIELD = "CANNED_RESPONSE";

// Values emitted into the warm-up call. Dummy credentials and a local endpoint keep the call offline; a 200 status
// exercises the success path.
private static final int SUCCESS_STATUS_CODE = 200;
private static final String DUMMY_ACCESS_KEY_ID = "akid";
private static final String DUMMY_SECRET_ACCESS_KEY = "skid";
private static final String LOCAL_ENDPOINT = "http://localhost";

private static final ClassName CANNED_RESPONSE_HTTP_CLIENT =
ClassName.get("software.amazon.awssdk.core.internal.crac", "CannedResponseHttpClient");
private static final ClassName CANNED_RESPONSE_ASYNC_HTTP_CLIENT =
ClassName.get("software.amazon.awssdk.core.internal.crac", "CannedResponseAsyncHttpClient");
private static final ClassName SDK_HTTP_CLIENT =
ClassName.get("software.amazon.awssdk.http", "SdkHttpClient");
private static final ClassName SDK_ASYNC_HTTP_CLIENT =
ClassName.get("software.amazon.awssdk.http.async", "SdkAsyncHttpClient");
private static final ClassName STATIC_CREDENTIALS_PROVIDER =
ClassName.get("software.amazon.awssdk.auth.credentials", "StaticCredentialsProvider");
private static final ClassName AWS_BASIC_CREDENTIALS =
ClassName.get("software.amazon.awssdk.auth.credentials", "AwsBasicCredentials");
private static final ClassName REGION =
ClassName.get("software.amazon.awssdk.regions", "Region");

private final IntermediateModel model;
private final PoetExtension poetExtensions;

public WarmUpProviderSpec(IntermediateModel model) {
this.model = model;
this.poetExtensions = new PoetExtension(model);
}

@Override
Expand All @@ -50,14 +85,86 @@ public TypeSpec poetSpec() {
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addAnnotation(SdkInternalApi.class)
.addSuperinterface(SdkWarmUpProvider.class)
.addField(cannedResponseField())
.addMethod(warmUpMethod())
.build();
}

private MethodSpec warmUpMethod() {
CodeBlock.Builder body = CodeBlock.builder();
if (!model.getCustomizationConfig().isSkipSyncClientGeneration()) {
body.add(clientBlock(poetExtensions.getClientClass(model.getMetadata().getSyncInterface()),
CANNED_RESPONSE_HTTP_CLIENT, SDK_HTTP_CLIENT, "httpClient", "client"));
}
body.add(clientBlock(poetExtensions.getClientClass(model.getMetadata().getAsyncInterface()),
CANNED_RESPONSE_ASYNC_HTTP_CLIENT, SDK_ASYNC_HTTP_CLIENT, "asyncHttpClient", "asyncClient"));

return MethodSpec.methodBuilder("warmUp")
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.addCode(body.build())
.build();
}

/**
* Emits a canned HTTP client plus a try-with-resources that builds and closes {@code clientType}. The sync and async
* paths differ only in these types and variable names, so they share this emitter.
*/
private CodeBlock clientBlock(ClassName clientType, ClassName cannedHttpClientType, ClassName httpClientType,
String httpClientVar, String clientVar) {
return CodeBlock.builder()
.addStatement("$T $N = $T.builder().responseBody($L).statusCode($L).build()",
httpClientType, httpClientVar, cannedHttpClientType, CANNED_RESPONSE_FIELD,
SUCCESS_STATUS_CODE)
.beginControlFlow("try ($1T $2N = $1T.builder()\n"
+ ".httpClient($3N)\n"
+ ".credentialsProvider($4T.create($5T.create($6S, $7S)))\n"
+ ".region($8T.US_EAST_1)\n"
+ ".endpointOverride($9T.create($10S))\n"
+ ".build())",
clientType,
clientVar,
httpClientVar,
STATIC_CREDENTIALS_PROVIDER,
AWS_BASIC_CREDENTIALS,
DUMMY_ACCESS_KEY_ID, DUMMY_SECRET_ACCESS_KEY,
REGION,
URI.class,
LOCAL_ENDPOINT)
.endControlFlow()
.build();
}

private FieldSpec cannedResponseField() {
return FieldSpec.builder(byte[].class, CANNED_RESPONSE_FIELD,
Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
.initializer(cannedResponseInitializer(model.getMetadata().getProtocol()))
.build();
}

/**
* A minimal, valid 200 response body per protocol. An empty body unmarshals for every protocol.
*/
private CodeBlock cannedResponseInitializer(Protocol protocol) {
switch (protocol) {
case REST_JSON:
case AWS_JSON:
return textInitializer("{}");
case QUERY:
case EC2:
case REST_XML:
return textInitializer("<Response/>");
case CBOR:
case SMITHY_RPC_V2_CBOR:
// Both are binary CBOR protocols, so a text "{}" is not a valid body. 0xA0 is the single-byte CBOR
// encoding of an empty map, the equivalent of "{}" for JSON.
return CodeBlock.of("new byte[] {(byte) 0xA0}");
default:
throw new IllegalArgumentException("Unsupported protocol for CRaC warm-up canned response: " + protocol);
}
}

private CodeBlock textInitializer(String body) {
return CodeBlock.of("$S.getBytes($T.UTF_8)", body, StandardCharsets.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,39 @@
public class WarmUpProviderSpecTest {

@Test
public void warmUpProvider() {
public void warmUpProvider_queryProtocol_generatesSyncAndAsyncClients() {
ClassSpec spec = new WarmUpProviderSpec(ClientTestModels.queryServiceModels());
assertThat(spec, generatesTo("warmup-provider.java"));
}

@Test
public void warmUpProvider_whenSyncClientSkipped_generatesAsyncClientOnly() {
ClassSpec spec = new WarmUpProviderSpec(
ClientTestModels.queryServiceModelWithSpecialCustomization("customization-skip-sync.config"));
assertThat(spec, generatesTo("warmup-provider-async-only.java"));
}

@Test
public void warmUpProvider_restJsonProtocol_usesJsonCannedResponse() {
ClassSpec spec = new WarmUpProviderSpec(ClientTestModels.restJsonServiceModels());
assertThat(spec, generatesTo("warmup-provider-rest-json.java"));
}

@Test
public void warmUpProvider_restXmlProtocol_usesXmlCannedResponse() {
ClassSpec spec = new WarmUpProviderSpec(ClientTestModels.xmlServiceModels());
assertThat(spec, generatesTo("warmup-provider-xml.java"));
}

@Test
public void warmUpProvider_smithyRpcV2CborProtocol_usesEmptyCborMapCannedResponse() {
ClassSpec spec = new WarmUpProviderSpec(ClientTestModels.rpcv2ServiceModels());
assertThat(spec, generatesTo("warmup-provider-rpcv2.java"));
}

@Test
public void warmUpProvider_cborProtocol_usesEmptyCborMapCannedResponse() {
ClassSpec spec = new WarmUpProviderSpec(ClientTestModels.cborServiceModels());
assertThat(spec, generatesTo("warmup-provider-cbor.java"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"skipSyncClientGeneration": true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package software.amazon.awssdk.services.query.internal.crac;

import java.net.URI;
import java.nio.charset.StandardCharsets;
import software.amazon.awssdk.annotations.Generated;
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.core.crac.SdkWarmUpProvider;
import software.amazon.awssdk.core.internal.crac.CannedResponseAsyncHttpClient;
import software.amazon.awssdk.http.async.SdkAsyncHttpClient;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.query.QueryAsyncClient;

@Generated("software.amazon.awssdk:codegen")
@SdkInternalApi
public final class QueryWarmUpProvider implements SdkWarmUpProvider {
private static final byte[] CANNED_RESPONSE = "<Response/>".getBytes(StandardCharsets.UTF_8);

@Override
public void warmUp() {
SdkAsyncHttpClient asyncHttpClient = CannedResponseAsyncHttpClient.builder().responseBody(CANNED_RESPONSE)
.statusCode(200).build();
try (QueryAsyncClient asyncClient = QueryAsyncClient.builder().httpClient(asyncHttpClient)
.credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create("akid", "skid")))
.region(Region.US_EAST_1).endpointOverride(URI.create("http://localhost")).build()) {
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package software.amazon.awssdk.services.json.internal.crac;

import java.net.URI;
import software.amazon.awssdk.annotations.Generated;
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.core.crac.SdkWarmUpProvider;
import software.amazon.awssdk.core.internal.crac.CannedResponseAsyncHttpClient;
import software.amazon.awssdk.core.internal.crac.CannedResponseHttpClient;
import software.amazon.awssdk.http.SdkHttpClient;
import software.amazon.awssdk.http.async.SdkAsyncHttpClient;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.json.JsonAsyncClient;
import software.amazon.awssdk.services.json.JsonClient;

@Generated("software.amazon.awssdk:codegen")
@SdkInternalApi
public final class JsonWarmUpProvider implements SdkWarmUpProvider {
private static final byte[] CANNED_RESPONSE = new byte[] { (byte) 0xA0 };

@Override
public void warmUp() {
SdkHttpClient httpClient = CannedResponseHttpClient.builder().responseBody(CANNED_RESPONSE).statusCode(200).build();
try (JsonClient client = JsonClient.builder().httpClient(httpClient)
.credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create("akid", "skid")))
.region(Region.US_EAST_1).endpointOverride(URI.create("http://localhost")).build()) {
}
SdkAsyncHttpClient asyncHttpClient = CannedResponseAsyncHttpClient.builder().responseBody(CANNED_RESPONSE)
.statusCode(200).build();
try (JsonAsyncClient asyncClient = JsonAsyncClient.builder().httpClient(asyncHttpClient)
.credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create("akid", "skid")))
.region(Region.US_EAST_1).endpointOverride(URI.create("http://localhost")).build()) {
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package software.amazon.awssdk.services.json.internal.crac;

import java.net.URI;
import java.nio.charset.StandardCharsets;
import software.amazon.awssdk.annotations.Generated;
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.core.crac.SdkWarmUpProvider;
import software.amazon.awssdk.core.internal.crac.CannedResponseAsyncHttpClient;
import software.amazon.awssdk.core.internal.crac.CannedResponseHttpClient;
import software.amazon.awssdk.http.SdkHttpClient;
import software.amazon.awssdk.http.async.SdkAsyncHttpClient;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.json.JsonAsyncClient;
import software.amazon.awssdk.services.json.JsonClient;

@Generated("software.amazon.awssdk:codegen")
@SdkInternalApi
public final class JsonWarmUpProvider implements SdkWarmUpProvider {
private static final byte[] CANNED_RESPONSE = "{}".getBytes(StandardCharsets.UTF_8);

@Override
public void warmUp() {
SdkHttpClient httpClient = CannedResponseHttpClient.builder().responseBody(CANNED_RESPONSE).statusCode(200).build();
try (JsonClient client = JsonClient.builder().httpClient(httpClient)
.credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create("akid", "skid")))
.region(Region.US_EAST_1).endpointOverride(URI.create("http://localhost")).build()) {
}
SdkAsyncHttpClient asyncHttpClient = CannedResponseAsyncHttpClient.builder().responseBody(CANNED_RESPONSE)
.statusCode(200).build();
try (JsonAsyncClient asyncClient = JsonAsyncClient.builder().httpClient(asyncHttpClient)
.credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create("akid", "skid")))
.region(Region.US_EAST_1).endpointOverride(URI.create("http://localhost")).build()) {
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package software.amazon.awssdk.services.smithyrpcv2protocol.internal.crac;

import java.net.URI;
import software.amazon.awssdk.annotations.Generated;
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.core.crac.SdkWarmUpProvider;
import software.amazon.awssdk.core.internal.crac.CannedResponseAsyncHttpClient;
import software.amazon.awssdk.core.internal.crac.CannedResponseHttpClient;
import software.amazon.awssdk.http.SdkHttpClient;
import software.amazon.awssdk.http.async.SdkAsyncHttpClient;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.smithyrpcv2protocol.SmithyRpcV2ProtocolAsyncClient;
import software.amazon.awssdk.services.smithyrpcv2protocol.SmithyRpcV2ProtocolClient;

@Generated("software.amazon.awssdk:codegen")
@SdkInternalApi
public final class SmithyRpcV2ProtocolWarmUpProvider implements SdkWarmUpProvider {
private static final byte[] CANNED_RESPONSE = new byte[] { (byte) 0xA0 };

@Override
public void warmUp() {
SdkHttpClient httpClient = CannedResponseHttpClient.builder().responseBody(CANNED_RESPONSE).statusCode(200).build();
try (SmithyRpcV2ProtocolClient client = SmithyRpcV2ProtocolClient.builder().httpClient(httpClient)
.credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create("akid", "skid")))
.region(Region.US_EAST_1).endpointOverride(URI.create("http://localhost")).build()) {
}
SdkAsyncHttpClient asyncHttpClient = CannedResponseAsyncHttpClient.builder().responseBody(CANNED_RESPONSE)
.statusCode(200).build();
try (SmithyRpcV2ProtocolAsyncClient asyncClient = SmithyRpcV2ProtocolAsyncClient.builder().httpClient(asyncHttpClient)
.credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create("akid", "skid")))
.region(Region.US_EAST_1).endpointOverride(URI.create("http://localhost")).build()) {
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package software.amazon.awssdk.services.xml.internal.crac;

import java.net.URI;
import java.nio.charset.StandardCharsets;
import software.amazon.awssdk.annotations.Generated;
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.core.crac.SdkWarmUpProvider;
import software.amazon.awssdk.core.internal.crac.CannedResponseAsyncHttpClient;
import software.amazon.awssdk.core.internal.crac.CannedResponseHttpClient;
import software.amazon.awssdk.http.SdkHttpClient;
import software.amazon.awssdk.http.async.SdkAsyncHttpClient;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.xml.XmlAsyncClient;
import software.amazon.awssdk.services.xml.XmlClient;

@Generated("software.amazon.awssdk:codegen")
@SdkInternalApi
public final class XmlWarmUpProvider implements SdkWarmUpProvider {
private static final byte[] CANNED_RESPONSE = "<Response/>".getBytes(StandardCharsets.UTF_8);

@Override
public void warmUp() {
SdkHttpClient httpClient = CannedResponseHttpClient.builder().responseBody(CANNED_RESPONSE).statusCode(200).build();
try (XmlClient client = XmlClient.builder().httpClient(httpClient)
.credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create("akid", "skid")))
.region(Region.US_EAST_1).endpointOverride(URI.create("http://localhost")).build()) {
}
SdkAsyncHttpClient asyncHttpClient = CannedResponseAsyncHttpClient.builder().responseBody(CANNED_RESPONSE)
.statusCode(200).build();
try (XmlAsyncClient asyncClient = XmlAsyncClient.builder().httpClient(asyncHttpClient)
.credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create("akid", "skid")))
.region(Region.US_EAST_1).endpointOverride(URI.create("http://localhost")).build()) {
}
}
}
Loading
Loading