diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/emitters/GeneratorPathProvider.java b/codegen/src/main/java/software/amazon/awssdk/codegen/emitters/GeneratorPathProvider.java
index 900205933515..3c3e4f83fada 100644
--- a/codegen/src/main/java/software/amazon/awssdk/codegen/emitters/GeneratorPathProvider.java
+++ b/codegen/src/main/java/software/amazon/awssdk/codegen/emitters/GeneratorPathProvider.java
@@ -107,4 +107,8 @@ public String getAuthSchemeInternalDirectory() {
public String getJmesPathInternalDirectory() {
return sourceDirectory + "/" + Utils.packageToDirectory(model.getMetadata().getFullInternalJmesPathPackageName());
}
+
+ public String getWarmUpProviderDirectory() {
+ return sourceDirectory + "/" + Utils.packageToDirectory(model.getMetadata().getFullCracInternalPackageName());
+ }
}
diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/emitters/WarmUpProviderRegistrationTask.java b/codegen/src/main/java/software/amazon/awssdk/codegen/emitters/WarmUpProviderRegistrationTask.java
new file mode 100644
index 000000000000..9185048cd1ce
--- /dev/null
+++ b/codegen/src/main/java/software/amazon/awssdk/codegen/emitters/WarmUpProviderRegistrationTask.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.codegen.emitters;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.util.List;
+
+/**
+ * Appends a generated {@code SdkWarmUpProvider} implementation's fully-qualified class name to the shared
+ * {@code META-INF/services/software.amazon.awssdk.core.crac.SdkWarmUpProvider} resource file.
+ *
+ *
This task writes a plain resource file rather than a {@code .java} file, so it does not use
+ * {@link SimpleGeneratorTask}. It creates the file once and appends to it for each subsequent service, and
+ * re-registering the same class name is a no-op.
+ */
+public final class WarmUpProviderRegistrationTask extends GeneratorTask {
+
+ private static final String SPI_RESOURCE_PATH =
+ "META-INF/services/software.amazon.awssdk.core.crac.SdkWarmUpProvider";
+
+ private final String resourcesDirectory;
+ private final String providerClassName;
+
+ public WarmUpProviderRegistrationTask(String resourcesDirectory, String providerClassName) {
+ this.resourcesDirectory = resourcesDirectory;
+ this.providerClassName = providerClassName;
+ }
+
+ @Override
+ public void compute() {
+ try {
+ Path file = Paths.get(resourcesDirectory, SPI_RESOURCE_PATH);
+ Path parent = file.getParent();
+ if (parent != null) {
+ Files.createDirectories(parent);
+ }
+
+ if (Files.exists(file)) {
+ List lines = Files.readAllLines(file, StandardCharsets.UTF_8);
+ if (lines.contains(providerClassName)) {
+ return;
+ }
+ }
+
+ Files.write(file,
+ (providerClassName + "\n").getBytes(StandardCharsets.UTF_8),
+ StandardOpenOption.CREATE,
+ StandardOpenOption.APPEND);
+ } catch (IOException e) {
+ throw new UncheckedIOException("Failed to register warm up provider " + providerClassName, e);
+ }
+ }
+}
diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/emitters/tasks/AwsGeneratorTasks.java b/codegen/src/main/java/software/amazon/awssdk/codegen/emitters/tasks/AwsGeneratorTasks.java
index a6b3b75e4fcc..888a7cbce01b 100644
--- a/codegen/src/main/java/software/amazon/awssdk/codegen/emitters/tasks/AwsGeneratorTasks.java
+++ b/codegen/src/main/java/software/amazon/awssdk/codegen/emitters/tasks/AwsGeneratorTasks.java
@@ -28,6 +28,7 @@ public AwsGeneratorTasks(GeneratorTaskParams params) {
new EventStreamGeneratorTasks(params),
new WaitersGeneratorTasks(params),
new EndpointProviderTasks(params),
- new AuthSchemeGeneratorTasks(params));
+ new AuthSchemeGeneratorTasks(params),
+ new WarmUpProviderTasks(params));
}
}
diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/emitters/tasks/WarmUpProviderTasks.java b/codegen/src/main/java/software/amazon/awssdk/codegen/emitters/tasks/WarmUpProviderTasks.java
new file mode 100644
index 000000000000..bc947d4e0ee7
--- /dev/null
+++ b/codegen/src/main/java/software/amazon/awssdk/codegen/emitters/tasks/WarmUpProviderTasks.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.codegen.emitters.tasks;
+
+import java.util.ArrayList;
+import java.util.List;
+import software.amazon.awssdk.codegen.emitters.GeneratorTask;
+import software.amazon.awssdk.codegen.emitters.GeneratorTaskParams;
+import software.amazon.awssdk.codegen.emitters.PoetGeneratorTask;
+import software.amazon.awssdk.codegen.emitters.WarmUpProviderRegistrationTask;
+import software.amazon.awssdk.codegen.poet.crac.WarmUpProviderSpec;
+
+/**
+ * Emits the per-service {@code SdkWarmUpProvider} implementation and its {@code META-INF/services} registration.
+ */
+public final class WarmUpProviderTasks extends BaseGeneratorTasks {
+
+ private final GeneratorTaskParams params;
+
+ public WarmUpProviderTasks(GeneratorTaskParams params) {
+ super(params);
+ this.params = params;
+ }
+
+ @Override
+ protected List createTasks() throws Exception {
+ List tasks = new ArrayList<>();
+
+ WarmUpProviderSpec spec = new WarmUpProviderSpec(model);
+
+ tasks.add(new PoetGeneratorTask(warmUpProviderDir(), model.getFileHeader(), spec));
+ tasks.add(new WarmUpProviderRegistrationTask(params.getPathProvider().getResourcesDirectory(),
+ spec.className().toString()));
+ return tasks;
+ }
+
+ private String warmUpProviderDir() {
+ return params.getPathProvider().getWarmUpProviderDirectory();
+ }
+}
diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/Metadata.java b/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/Metadata.java
index 4eb10f6bf105..402febeb86ad 100644
--- a/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/Metadata.java
+++ b/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/Metadata.java
@@ -775,6 +775,10 @@ public String getFullInternalPackageName() {
return joinPackageNames(getFullClientPackageName(), "internal");
}
+ public String getFullCracInternalPackageName() {
+ return joinPackageNames(getFullInternalPackageName(), "crac");
+ }
+
public Metadata setJmesPathPackageName(String jmesPathPackageName) {
this.jmesPathPackageName = jmesPathPackageName;
return this;
diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/crac/WarmUpProviderSpec.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/crac/WarmUpProviderSpec.java
new file mode 100644
index 000000000000..482164c30cc0
--- /dev/null
+++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/crac/WarmUpProviderSpec.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.codegen.poet.crac;
+
+import com.squareup.javapoet.ClassName;
+import com.squareup.javapoet.MethodSpec;
+import com.squareup.javapoet.TypeSpec;
+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.poet.ClassSpec;
+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.
+ */
+public class WarmUpProviderSpec implements ClassSpec {
+
+ private final IntermediateModel model;
+
+ public WarmUpProviderSpec(IntermediateModel model) {
+ this.model = model;
+ }
+
+ @Override
+ public ClassName className() {
+ return ClassName.get(model.getMetadata().getFullCracInternalPackageName(),
+ model.getMetadata().getServiceName() + "WarmUpProvider");
+ }
+
+ @Override
+ public TypeSpec poetSpec() {
+ return PoetUtils.createClassBuilder(className())
+ .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
+ .addAnnotation(SdkInternalApi.class)
+ .addSuperinterface(SdkWarmUpProvider.class)
+ .addMethod(warmUpMethod())
+ .build();
+ }
+
+ private MethodSpec warmUpMethod() {
+ return MethodSpec.methodBuilder("warmUp")
+ .addAnnotation(Override.class)
+ .addModifiers(Modifier.PUBLIC)
+ .build();
+ }
+}
diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/emitters/GeneratorPathProviderTest.java b/codegen/src/test/java/software/amazon/awssdk/codegen/emitters/GeneratorPathProviderTest.java
new file mode 100644
index 000000000000..0a495d695cd7
--- /dev/null
+++ b/codegen/src/test/java/software/amazon/awssdk/codegen/emitters/GeneratorPathProviderTest.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.codegen.emitters;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.jupiter.api.Test;
+import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel;
+import software.amazon.awssdk.codegen.model.intermediate.Metadata;
+
+public class GeneratorPathProviderTest {
+
+ @Test
+ public void warmUpProviderDirectory_isUnderSourceDirInCracInternalPackage() {
+ Metadata metadata = new Metadata();
+ metadata.setRootPackageName("software.amazon.awssdk.services");
+ metadata.setClientPackageName("query");
+
+ IntermediateModel model = new IntermediateModel();
+ model.setMetadata(metadata);
+
+ GeneratorPathProvider provider =
+ new GeneratorPathProvider(model, "/src", "/test", "/resources");
+
+ assertThat(provider.getWarmUpProviderDirectory())
+ .isEqualTo("/src/software/amazon/awssdk/services/query/internal/crac");
+ }
+}
diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/emitters/WarmUpProviderRegistrationTaskTest.java b/codegen/src/test/java/software/amazon/awssdk/codegen/emitters/WarmUpProviderRegistrationTaskTest.java
new file mode 100644
index 000000000000..75cce7e5af4f
--- /dev/null
+++ b/codegen/src/test/java/software/amazon/awssdk/codegen/emitters/WarmUpProviderRegistrationTaskTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.codegen.emitters;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static software.amazon.awssdk.codegen.poet.ClientTestModels.queryServiceModels;
+import static software.amazon.awssdk.codegen.poet.ClientTestModels.xmlServiceModels;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+import software.amazon.awssdk.codegen.poet.crac.WarmUpProviderSpec;
+
+public class WarmUpProviderRegistrationTaskTest {
+
+ private static final String SPI_PATH =
+ "META-INF/services/software.amazon.awssdk.core.crac.SdkWarmUpProvider";
+
+ private static final String QUERY_PROVIDER = new WarmUpProviderSpec(queryServiceModels()).className().toString();
+ private static final String XML_PROVIDER = new WarmUpProviderSpec(xmlServiceModels()).className().toString();
+
+ @TempDir
+ Path resourcesDir;
+
+ @Test
+ public void writesProviderClassName() throws IOException {
+ new WarmUpProviderRegistrationTask(resourcesDir.toString(), QUERY_PROVIDER).compute();
+
+ List lines = Files.readAllLines(resourcesDir.resolve(SPI_PATH), StandardCharsets.UTF_8);
+ assertThat(lines).contains(QUERY_PROVIDER);
+ }
+
+ @Test
+ public void appendsSecondProviderWithoutClobbering() throws IOException {
+ new WarmUpProviderRegistrationTask(resourcesDir.toString(), QUERY_PROVIDER).compute();
+ new WarmUpProviderRegistrationTask(resourcesDir.toString(), XML_PROVIDER).compute();
+
+ List lines = Files.readAllLines(resourcesDir.resolve(SPI_PATH), StandardCharsets.UTF_8);
+ assertThat(lines).contains(QUERY_PROVIDER).contains(XML_PROVIDER);
+ }
+
+ @Test
+ public void doesNotDuplicateOnSecondIdenticalRun() throws IOException {
+ new WarmUpProviderRegistrationTask(resourcesDir.toString(), QUERY_PROVIDER).compute();
+ new WarmUpProviderRegistrationTask(resourcesDir.toString(), QUERY_PROVIDER).compute();
+
+ List lines = Files.readAllLines(resourcesDir.resolve(SPI_PATH), StandardCharsets.UTF_8);
+ long count = lines.stream().filter(l -> l.equals(QUERY_PROVIDER)).count();
+ assertThat(count).isEqualTo(1);
+ }
+}
diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/model/intermediate/MetadataTest.java b/codegen/src/test/java/software/amazon/awssdk/codegen/model/intermediate/MetadataTest.java
new file mode 100644
index 000000000000..9541a3fc9386
--- /dev/null
+++ b/codegen/src/test/java/software/amazon/awssdk/codegen/model/intermediate/MetadataTest.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.codegen.model.intermediate;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.jupiter.api.Test;
+
+public class MetadataTest {
+
+ @Test
+ public void fullCracInternalPackageName_appendsInternalCracToClientPackage() {
+ Metadata metadata = new Metadata();
+ metadata.setRootPackageName("software.amazon.awssdk.services");
+ metadata.setClientPackageName("query");
+
+ assertThat(metadata.getFullCracInternalPackageName())
+ .isEqualTo("software.amazon.awssdk.services.query.internal.crac");
+ }
+}
diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/crac/WarmUpProviderSpecTest.java b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/crac/WarmUpProviderSpecTest.java
new file mode 100644
index 000000000000..2a69773e2a34
--- /dev/null
+++ b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/crac/WarmUpProviderSpecTest.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.codegen.poet.crac;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static software.amazon.awssdk.codegen.poet.PoetMatchers.generatesTo;
+
+import org.junit.jupiter.api.Test;
+import software.amazon.awssdk.codegen.poet.ClassSpec;
+import software.amazon.awssdk.codegen.poet.ClientTestModels;
+
+public class WarmUpProviderSpecTest {
+
+ @Test
+ public void warmUpProvider() {
+ ClassSpec spec = new WarmUpProviderSpec(ClientTestModels.queryServiceModels());
+ assertThat(spec, generatesTo("warmup-provider.java"));
+ }
+}
diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/crac/warmup-provider.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/crac/warmup-provider.java
new file mode 100644
index 000000000000..9b92d51d2d71
--- /dev/null
+++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/crac/warmup-provider.java
@@ -0,0 +1,13 @@
+package software.amazon.awssdk.services.query.internal.crac;
+
+import software.amazon.awssdk.annotations.Generated;
+import software.amazon.awssdk.annotations.SdkInternalApi;
+import software.amazon.awssdk.core.crac.SdkWarmUpProvider;
+
+@Generated("software.amazon.awssdk:codegen")
+@SdkInternalApi
+public final class QueryWarmUpProvider implements SdkWarmUpProvider {
+ @Override
+ public void warmUp() {
+ }
+}