messages) {
+ this.messages = messages;
+ }
+
+ @Override
+ public void addError(AuditEvent event) {
+ messages.add(event.getMessage());
+ }
+
+ @Override
+ public void addException(AuditEvent event, Throwable throwable) {
+ messages.add("EXCEPTION: " + throwable.getMessage());
+ }
+
+ @Override
+ public void auditStarted(AuditEvent event) {
+ }
+
+ @Override
+ public void auditFinished(AuditEvent event) {
+ }
+
+ @Override
+ public void fileStarted(AuditEvent event) {
+ }
+
+ @Override
+ public void fileFinished(AuditEvent event) {
+ }
+ }
+}
diff --git a/core/annotations/src/main/java/software/amazon/awssdk/annotations/SdkAdvancedApi.java b/core/annotations/src/main/java/software/amazon/awssdk/annotations/SdkAdvancedApi.java
new file mode 100644
index 000000000000..e7d5dab98bcb
--- /dev/null
+++ b/core/annotations/src/main/java/software/amazon/awssdk/annotations/SdkAdvancedApi.java
@@ -0,0 +1,76 @@
+/*
+ * 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.annotations;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marks an API that is error-prone to use: implementing, overriding, or calling it incorrectly, or
+ * configuring it with an unsafe value, compiles cleanly but can fail or misbehave at runtime rather
+ * than reporting a clear error.
+ *
+ * The annotation records the risk information on the API in a structured form: {@link #cautionWhen()}
+ * classifies which kind of use is error-prone, {@link #guidance()} explains the contract that must
+ * be upheld, {@link #saferAlternative()} points to a lower-risk approach, and {@link #link()} points
+ * to further documentation.
+ *
+ *
This is an advisory marker: it does not gate compilation and imposes no runtime behavior.
+ */
+@Documented
+@Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.FIELD})
+@Retention(RetentionPolicy.CLASS)
+@SdkProtectedApi
+public @interface SdkAdvancedApi {
+
+ /**
+ * Which kind of use of this API is error-prone: IMPLEMENTED if the risk is in implementing
+ * or extending the annotated type, OVERRIDDEN if it is in overriding the annotated method,
+ * CONFIGURED if it is in setting the annotated field or option, and CALLED if it is in
+ * calling the annotated method (for example a factory that accepts an object you supply and does
+ * not shield you from that object's contract). Required.
+ */
+ Usage cautionWhen();
+
+ /**
+ * Explains why this API is error-prone and what you must uphold to use it safely: the parts of
+ * the contract that are easy to get wrong and the failure that results if they are not met.
+ */
+ String guidance();
+
+ /**
+ * An optional pointer to the recommended safer approach that satisfies the same need
+ * without the risk (for example, a factory method that implements the contract
+ * correctly). Empty when there is no direct alternative.
+ */
+ String saferAlternative() default "";
+
+ /**
+ * An optional link to documentation explaining the risk and correct usage in more
+ * depth. Empty when there is no dedicated page.
+ */
+ String link() default "";
+
+ enum Usage {
+ IMPLEMENTED,
+ OVERRIDDEN,
+ CONFIGURED,
+ CALLED
+ }
+}
diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/async/AsyncRequestBody.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/async/AsyncRequestBody.java
index 36a2d7eb647c..d762dc15a809 100644
--- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/async/AsyncRequestBody.java
+++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/async/AsyncRequestBody.java
@@ -29,6 +29,8 @@
import java.util.function.Consumer;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
+import software.amazon.awssdk.annotations.SdkAdvancedApi;
+import software.amazon.awssdk.annotations.SdkAdvancedApi.Usage;
import software.amazon.awssdk.annotations.SdkProtectedApi;
import software.amazon.awssdk.annotations.SdkPublicApi;
import software.amazon.awssdk.core.FileRequestBodyConfiguration;
@@ -63,6 +65,15 @@
* @see ByteBuffersAsyncRequestBody
*/
@SdkPublicApi
+@SdkAdvancedApi(
+ cautionWhen = Usage.IMPLEMENTED,
+ guidance = "This is a reactive-streams Publisher and must obey the reactive-streams specification. "
+ + "The SDK re-subscribes on each retry attempt, "
+ + "so to support retries, reproduce the full content on every subscribe; a body that cannot should fail the "
+ + "subscriber on a later subscribe rather than emit partial content.",
+ saferAlternative = "Prefer the AsyncRequestBody.fromFile/fromBytes/fromInputStream factories. If you must supply a "
+ + "custom Publisher, build it with an established reactive-streams library such as RxJava or Reactor rather "
+ + "than implementing Publisher by hand.")
public interface AsyncRequestBody extends SdkPublisher {
/**
@@ -94,6 +105,16 @@ default String body() {
* @param publisher Publisher of source data
* @return Implementation of {@link AsyncRequestBody} that produces data send by the publisher
*/
+ @SdkAdvancedApi(
+ cautionWhen = Usage.CALLED,
+ guidance = "The returned AsyncRequestBody passes each subscribe straight through to the supplied publisher, so "
+ + "the publisher must itself meet the AsyncRequestBody contract: obey the reactive-streams "
+ + "specification, and since the SDK re-subscribes on each "
+ + "retry attempt, reproduce the full content on every subscribe to support retries; a publisher that "
+ + "cannot should fail the subscriber on a later subscribe rather than emit partial content.",
+ saferAlternative = "Prefer the AsyncRequestBody.fromFile/fromBytes/fromInputStream factories, which do not "
+ + "require the caller to supply a contract-compliant publisher. If you must supply one, build it with an "
+ + "established reactive-streams library such as RxJava or Reactor rather than by hand.")
static AsyncRequestBody fromPublisher(Publisher publisher) {
return new AsyncRequestBody() {
diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/async/AsyncResponseTransformer.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/async/AsyncResponseTransformer.java
index dd4a5140170f..70ff1e6aef50 100644
--- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/async/AsyncResponseTransformer.java
+++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/async/AsyncResponseTransformer.java
@@ -23,6 +23,8 @@
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
+import software.amazon.awssdk.annotations.SdkAdvancedApi;
+import software.amazon.awssdk.annotations.SdkAdvancedApi.Usage;
import software.amazon.awssdk.annotations.SdkProtectedApi;
import software.amazon.awssdk.annotations.SdkPublicApi;
import software.amazon.awssdk.core.FileTransformerConfiguration;
@@ -86,6 +88,15 @@
* @param Type this response handler produces. I.E. the type you are transforming the response into.
*/
@SdkPublicApi
+@SdkAdvancedApi(
+ cautionWhen = Usage.IMPLEMENTED,
+ guidance = "prepare() is called on each request attempt; if the CompletableFuture it returned on a previous "
+ + "attempt has already completed, it must return a new instance so the result of a retry is not lost, and "
+ + "exceptionOccurred() should release any resources the attempt opened (for example an open file channel) "
+ + "so retries do not leak them. onStream() receives the response body as a reactive-streams Publisher that "
+ + "the implementation subscribes to, and that subscriber must comply with the reactive-streams "
+ + "specification; a subscriber that never requests data stalls the response.",
+ saferAlternative = "Prefer the AsyncResponseTransformer.toFile/toBytes/toBlockingInputStream factories.")
public interface AsyncResponseTransformer {
/**
* Initial call to enable any setup required before the response is handled.
@@ -304,6 +315,14 @@ static AsyncResponseTransformer>
* @param Pojo response type.
* @return AsyncResponseTransformer instance.
*/
+ @SdkAdvancedApi(
+ cautionWhen = Usage.CALLED,
+ guidance = "The returned ResponsePublisher is a reactive-streams Publisher you must subscribe to and drive: "
+ + "your subscriber must obey the reactive-streams specification and honor back-pressure. A subscriber "
+ + "that never requests data stalls the response, and requesting unbounded data can exhaust memory.",
+ saferAlternative = "Prefer the fully-managed AsyncResponseTransformer.toFile/toBytes factories, which do not "
+ + "hand you a publisher to drive. If you do consume the publisher, use an established reactive-streams "
+ + "library such as RxJava or Reactor rather than a hand-written Subscriber.")
static AsyncResponseTransformer> toPublisher() {
return new PublisherAsyncResponseTransformer<>();
}
@@ -331,6 +350,14 @@ static AsyncResponseTransformer AsyncResponseTransformer> toPublisher(Duration timeout) {
return new PublisherAsyncResponseTransformer<>(timeout);
diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/config/SdkAdvancedAsyncClientOption.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/config/SdkAdvancedAsyncClientOption.java
index e37ba953211d..973fc0c97f1a 100644
--- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/config/SdkAdvancedAsyncClientOption.java
+++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/config/SdkAdvancedAsyncClientOption.java
@@ -18,6 +18,8 @@
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
+import software.amazon.awssdk.annotations.SdkAdvancedApi;
+import software.amazon.awssdk.annotations.SdkAdvancedApi.Usage;
import software.amazon.awssdk.annotations.SdkPublicApi;
/**
@@ -52,6 +54,13 @@ public final class SdkAdvancedAsyncClientOption extends ClientOption {
* within the I/O thread because it may block the I/O thread and cause deadlock, especially if you are sending
* another SDK request in the {@link CompletableFuture} chain since the SDK may perform blocking calls in some cases.
*/
+ @SdkAdvancedApi(
+ cautionWhen = Usage.CONFIGURED,
+ guidance = "An executor that does not dispatch to a separate thread (e.g. Runnable::run) runs the "
+ + "completion inline on the calling thread and can deadlock the client, especially when the "
+ + "future chain issues another SDK request. If you set a custom executor, verify it under your "
+ + "own load tests, including request chains that issue further SDK calls.",
+ saferAlternative = "Leave unset to use the SDK-managed per-client ThreadPoolExecutor.")
public static final SdkAdvancedAsyncClientOption FUTURE_COMPLETION_EXECUTOR =
new SdkAdvancedAsyncClientOption<>(Executor.class);
diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/interceptor/ExecutionInterceptor.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/interceptor/ExecutionInterceptor.java
index 7ade08249da2..73326f170095 100644
--- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/interceptor/ExecutionInterceptor.java
+++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/interceptor/ExecutionInterceptor.java
@@ -19,6 +19,8 @@
import java.nio.ByteBuffer;
import java.util.Optional;
import org.reactivestreams.Publisher;
+import software.amazon.awssdk.annotations.SdkAdvancedApi;
+import software.amazon.awssdk.annotations.SdkAdvancedApi.Usage;
import software.amazon.awssdk.annotations.SdkPublicApi;
import software.amazon.awssdk.core.SdkRequest;
import software.amazon.awssdk.core.SdkResponse;
@@ -183,11 +185,32 @@ default SdkHttpRequest modifyHttpRequest(Context.ModifyHttpRequest context,
return context.httpRequest();
}
+ @SdkAdvancedApi(
+ cautionWhen = Usage.OVERRIDDEN,
+ guidance = "Replacing the request body from an interceptor is discouraged: the returned RequestBody becomes the "
+ + "content the SDK sends, and the request's Content-Length and content hash or checksum are derived from "
+ + "it, so a body whose content or length does not match what the operation expects corrupts the request "
+ + "or is rejected by the service. Return the body reported by context.requestBody() unless you have a "
+ + "specific reason to replace it.",
+ saferAlternative = "Return context.requestBody() unchanged. If you must supply a different body, build it with "
+ + "the RequestBody factories rather than a hand-written ContentStreamProvider.")
default Optional modifyHttpContent(Context.ModifyHttpRequest context,
ExecutionAttributes executionAttributes) {
return context.requestBody();
}
+ @SdkAdvancedApi(
+ cautionWhen = Usage.OVERRIDDEN,
+ guidance = "Replacing the async request body from an interceptor is discouraged: the returned AsyncRequestBody "
+ + "becomes the content the SDK sends, and the request's Content-Length and content hash or checksum are "
+ + "derived from it, so a body whose content or length does not match what the operation expects corrupts "
+ + "the request or is rejected by the service. It must also comply with the reactive-streams "
+ + "specification; the SDK re-subscribes to it "
+ + "on each retry attempt, so to support retries it must reproduce the full content on every subscribe. "
+ + "Return the body reported by context.asyncRequestBody() unless you have a specific reason to replace "
+ + "it.",
+ saferAlternative = "Return context.asyncRequestBody() unchanged. If you must supply a different body, build it "
+ + "with the AsyncRequestBody factories or an established reactive-streams library.")
default Optional modifyAsyncHttpContent(Context.ModifyHttpRequest context,
ExecutionAttributes executionAttributes) {
return context.asyncRequestBody();
diff --git a/http-client-spi/src/main/java/software/amazon/awssdk/http/ContentStreamProvider.java b/http-client-spi/src/main/java/software/amazon/awssdk/http/ContentStreamProvider.java
index 4ae96838d8b4..714a960a9262 100644
--- a/http-client-spi/src/main/java/software/amazon/awssdk/http/ContentStreamProvider.java
+++ b/http-client-spi/src/main/java/software/amazon/awssdk/http/ContentStreamProvider.java
@@ -24,6 +24,8 @@
import java.util.Arrays;
import java.util.Map;
import java.util.function.Supplier;
+import software.amazon.awssdk.annotations.SdkAdvancedApi;
+import software.amazon.awssdk.annotations.SdkAdvancedApi.Usage;
import software.amazon.awssdk.annotations.SdkProtectedApi;
import software.amazon.awssdk.annotations.SdkPublicApi;
import software.amazon.awssdk.utils.IoUtils;
@@ -41,6 +43,13 @@
*/
@SdkPublicApi
@FunctionalInterface
+@SdkAdvancedApi(
+ cautionWhen = Usage.IMPLEMENTED,
+ guidance = "newStream() must return a stream positioned at the start of the content on every call, closing any "
+ + "previous stream it opened. A stale or unreset stream corrupts the request body and breaks retries, "
+ + "which re-read the content.",
+ saferAlternative = "Prefer the ContentStreamProvider.fromInputStream/fromByteArray/fromString factories, which "
+ + "handle reset correctly.")
public interface ContentStreamProvider {
/**
* Create {@link ContentStreamProvider} from a byte array. This will copy the contents of the byte array.