From 553f62aa9155fc34dfd9f4b0b0adae51f57dfbf2 Mon Sep 17 00:00:00 2001
From: jencymaryjoseph <35571282+jencymaryjoseph@users.noreply.github.com>
Date: Wed, 2 Jul 2025 15:09:30 -0700
Subject: [PATCH 01/30] Adding pre-signed URL operation core API and Get Object
Request Pojo (#6216)
* Add presigned URL request models and tests
* Pre-signed URL API and Get Object Request definitions
* JavaDoc changes, String to URL change
* Delete services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/presignedurl/DefaultPresignedUrlManager.java
* Delete services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/presignedurl/PresignedUrlGetObjectRequestMarshaller.java
* Delete services/s3/src/test/java/software/amazon/awssdk/services/s3/presignedurl/model/PresignedUrlTest.java
* Format PresignedUrlGetObjectRequestWrapper.java
* Format PresignedUrlManager.java
* Format PresignedUrlGetObjectRequest.java
* Format PresignedUrlGetObjectRequestWrapperTest.java
* Format PresignedUrlGetObjectRequestTest.java
* sonarcube issues fixed
* Delete .idea/codeStyles/Project.xml
* added consumer builder, javadoc for builder, assert range value test
* removing codestle from gitignore
---
.../PresignedUrlGetObjectRequestWrapper.java | 153 ++++++++++++++++
.../s3/presignedurl/PresignedUrlManager.java | 84 +++++++++
.../model/PresignedUrlGetObjectRequest.java | 167 ++++++++++++++++++
...esignedUrlGetObjectRequestWrapperTest.java | 94 ++++++++++
.../PresignedUrlGetObjectRequestTest.java | 115 ++++++++++++
5 files changed, 613 insertions(+)
create mode 100644 services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/presignedurl/model/PresignedUrlGetObjectRequestWrapper.java
create mode 100644 services/s3/src/main/java/software/amazon/awssdk/services/s3/presignedurl/PresignedUrlManager.java
create mode 100644 services/s3/src/main/java/software/amazon/awssdk/services/s3/presignedurl/model/PresignedUrlGetObjectRequest.java
create mode 100644 services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/model/PresignedUrlGetObjectRequestWrapperTest.java
create mode 100644 services/s3/src/test/java/software/amazon/awssdk/services/s3/presignedurl/model/PresignedUrlGetObjectRequestTest.java
diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/presignedurl/model/PresignedUrlGetObjectRequestWrapper.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/presignedurl/model/PresignedUrlGetObjectRequestWrapper.java
new file mode 100644
index 000000000000..efd5a9f78167
--- /dev/null
+++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/presignedurl/model/PresignedUrlGetObjectRequestWrapper.java
@@ -0,0 +1,153 @@
+/*
+ * 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.services.s3.internal.presignedurl.model;
+
+import java.net.URL;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Function;
+import software.amazon.awssdk.annotations.SdkInternalApi;
+import software.amazon.awssdk.core.SdkField;
+import software.amazon.awssdk.core.protocol.MarshallLocation;
+import software.amazon.awssdk.core.protocol.MarshallingType;
+import software.amazon.awssdk.core.traits.LocationTrait;
+import software.amazon.awssdk.services.s3.model.S3Request;
+
+/**
+ * Internal request object for presigned URL GetObject operations.
+ *
+ * This class is used internally by the AWS SDK to process presigned URL requests for S3 GetObject operations. It contains minimal
+ * SdkField definitions needed for custom marshalling and is not intended for direct use by SDK users.
+ *
+ * Note: This is an internal implementation class and should not be used
+ * directly. Use {@code PresignedUrlGetObjectRequest} for public API interactions.
+ */
+@SdkInternalApi
+public final class PresignedUrlGetObjectRequestWrapper extends S3Request {
+ private static final SdkField RANGE_FIELD = SdkField
+ .builder(MarshallingType.STRING)
+ .memberName("Range")
+ .getter(getter(PresignedUrlGetObjectRequestWrapper::range))
+ .traits(LocationTrait.builder().location(MarshallLocation.HEADER).locationName("Range")
+ .unmarshallLocationName("Range").build()).build();
+
+ private static final List> SDK_FIELDS = Collections.unmodifiableList(
+ Arrays.asList(RANGE_FIELD));
+
+ private static final Map> SDK_NAME_TO_FIELD = memberNameToFieldInitializer();
+
+ private final URL url;
+ private final String range;
+
+ private PresignedUrlGetObjectRequestWrapper(Builder builder) {
+ super(builder);
+ this.url = builder.url;
+ this.range = builder.range;
+ }
+
+ public URL url() {
+ return url;
+ }
+
+ public String range() {
+ return range;
+ }
+
+ @Override
+ public List> sdkFields() {
+ return SDK_FIELDS;
+ }
+
+ @Override
+ public Map> sdkFieldNameToField() {
+ return SDK_NAME_TO_FIELD;
+ }
+
+ private static Function getter(Function g) {
+ return obj -> g.apply((PresignedUrlGetObjectRequestWrapper) obj);
+ }
+
+ private static Map> memberNameToFieldInitializer() {
+ Map> map = new HashMap<>();
+ map.put("Range", RANGE_FIELD);
+ return Collections.unmodifiableMap(map);
+ }
+
+ @Override
+ public Builder toBuilder() {
+ return new Builder(this);
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ if (!super.equals(obj)) {
+ return false;
+ }
+ PresignedUrlGetObjectRequestWrapper that = (PresignedUrlGetObjectRequestWrapper) obj;
+ return Objects.equals(url, that.url) && Objects.equals(range, that.range);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = Objects.hashCode(super.hashCode());
+ result = 31 * result + Objects.hashCode(url);
+ result = 31 * result + Objects.hashCode(range);
+ return result;
+ }
+
+ public static final class Builder extends S3Request.BuilderImpl {
+ private URL url;
+ private String range;
+
+ public Builder() {
+ }
+
+ Builder(PresignedUrlGetObjectRequestWrapper request) {
+ super(request);
+ this.url = request.url();
+ this.range = request.range();
+ }
+
+ public Builder url(URL url) {
+ this.url = url;
+ return this;
+ }
+
+ public Builder range(String range) {
+ this.range = range;
+ return this;
+ }
+
+ @Override
+ public PresignedUrlGetObjectRequestWrapper build() {
+ return new PresignedUrlGetObjectRequestWrapper(this);
+ }
+ }
+}
diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/presignedurl/PresignedUrlManager.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/presignedurl/PresignedUrlManager.java
new file mode 100644
index 000000000000..3b005ce871da
--- /dev/null
+++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/presignedurl/PresignedUrlManager.java
@@ -0,0 +1,84 @@
+/*
+ * 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.services.s3.presignedurl;
+
+import software.amazon.awssdk.annotations.SdkPublicApi;
+import software.amazon.awssdk.core.exception.SdkClientException;
+import software.amazon.awssdk.core.sync.ResponseTransformer;
+import software.amazon.awssdk.services.s3.S3Client;
+import software.amazon.awssdk.services.s3.model.GetObjectResponse;
+import software.amazon.awssdk.services.s3.model.InvalidObjectStateException;
+import software.amazon.awssdk.services.s3.model.NoSuchKeyException;
+import software.amazon.awssdk.services.s3.model.S3Exception;
+import software.amazon.awssdk.services.s3.presignedurl.model.PresignedUrlGetObjectRequest;
+
+/**
+ * Interface for executing S3 operations using presigned URLs. This can be accessed using {@link S3Client#presignedUrlManager()}.
+ */
+@SdkPublicApi
+public interface PresignedUrlManager {
+ /**
+ *
+ * Downloads an S3 object using a presigned URL.
+ *
+ *
+ * This operation uses a presigned URL that contains all necessary authentication information, eliminating the need for AWS
+ * credentials at request time. The presigned URL must be valid and not expired.
+ *
+ *
+ * Range Requests
+ *
+ *
+ * Supports partial object downloads using HTTP Range headers. Specify the range parameter
+ * in the request to download only a portion of the object (e.g., "bytes=0-1023").
+ *
+ *
+ * Storage Classes
+ *
+ *
+ * If the object is stored in S3 Glacier Flexible Retrieval, S3 Glacier Deep Archive,
+ * or S3 Intelligent-Tiering Archive tiers, the object must be restored before retrieval.
+ * Otherwise, this operation returns an InvalidObjectState error.
+ *
+ *
+ *
+ *
+ * @param request The presigned URL request containing the URL and optional range parameters
+ * @param responseTransformer Transforms the response to the desired return type. See
+ * {@link software.amazon.awssdk.core.sync.ResponseTransformer} for pre-built implementations like
+ * downloading to a file or converting to bytes.
+ * @param The type of the transformed response
+ * @return The transformed result of the ResponseTransformer
+ * @throws software.amazon.awssdk.services.s3.model.NoSuchKeyException The specified object does not exist
+ * @throws software.amazon.awssdk.services.s3.model.InvalidObjectStateException Object is archived and must be restored before
+ * retrieval
+ * @throws software.amazon.awssdk.core.exception.SdkClientException If any client side error occurs such as
+ * network failures or invalid presigned URL
+ * @throws S3Exception Base class for all S3 service exceptions.
+ * Unknown exceptions will be thrown as an
+ * instance of this type.
+ */
+ default ReturnT getObject(PresignedUrlGetObjectRequest request,
+ ResponseTransformer responseTransformer) throws NoSuchKeyException,
+ InvalidObjectStateException,
+ SdkClientException,
+ S3Exception {
+ throw new UnsupportedOperationException();
+ }
+
+ //TODO: Add other getObject method flavors
+}
diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/presignedurl/model/PresignedUrlGetObjectRequest.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/presignedurl/model/PresignedUrlGetObjectRequest.java
new file mode 100644
index 000000000000..20cd9c89d8a2
--- /dev/null
+++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/presignedurl/model/PresignedUrlGetObjectRequest.java
@@ -0,0 +1,167 @@
+/*
+ * 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.services.s3.presignedurl.model;
+
+import java.net.URL;
+import java.util.Objects;
+import java.util.function.Consumer;
+import software.amazon.awssdk.annotations.SdkPublicApi;
+import software.amazon.awssdk.utils.ToString;
+import software.amazon.awssdk.utils.Validate;
+import software.amazon.awssdk.utils.builder.CopyableBuilder;
+import software.amazon.awssdk.utils.builder.ToCopyableBuilder;
+
+/**
+ * Request object for performing GetObject operations using a presigned URL.
+ */
+@SdkPublicApi
+public final class PresignedUrlGetObjectRequest implements ToCopyableBuilder {
+ private final URL presignedUrl;
+ private final String range;
+
+ private PresignedUrlGetObjectRequest(BuilderImpl builder) {
+ this.presignedUrl = builder.presignedUrl;
+ this.range = builder.range;
+ }
+
+ /**
+ *
+ * The presigned URL for the S3 object. This URL contains all necessary authentication information and can be used to download
+ * the object without additional credentials.
+ *
+ * Note: Presigned URLs have a limited lifetime and will expire after the
+ * specified duration. Ensure the URL is used before expiration.
+ *
+ * @return The presigned URL for the S3 object
+ */
+ public URL presignedUrl() {
+ return presignedUrl;
+ }
+
+ /**
+ *
+ * Specifies the byte range of an object. For more information about the HTTP Range header, see
+ *
+ * https://www.rfc-editor.org/rfc/rfc9110.html#name-range .
+ *
+ * Note: Amazon S3 doesn't support retrieving multiple ranges of data per GET request.
+ *
+ * @return The HTTP Range header value, or null if not specified.
+ */
+ public String range() {
+ return range;
+ }
+
+ @Override
+ public Builder toBuilder() {
+ return new BuilderImpl(this);
+ }
+
+ public static Builder builder() {
+ return new BuilderImpl();
+ }
+
+ /**
+ * Create a {@code PresignedUrlGetObjectRequest} instance with the given configuration
+ *
+ * @param builderConsumer the consumer that will configure the builder
+ * @return a {@code PresignedUrlGetObjectRequest} instance
+ */
+ public static PresignedUrlGetObjectRequest builder(Consumer builderConsumer) {
+ return builder().applyMutation(builderConsumer).build();
+ }
+
+ public static Class extends Builder> serializableBuilderClass() {
+ return BuilderImpl.class;
+ }
+
+ @Override
+ public int hashCode() {
+ int hashCode = 1;
+ hashCode = 31 * hashCode + Objects.hashCode(presignedUrl());
+ hashCode = 31 * hashCode + Objects.hashCode(range());
+ return hashCode;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ PresignedUrlGetObjectRequest other = (PresignedUrlGetObjectRequest) obj;
+ return Objects.equals(presignedUrl(), other.presignedUrl()) &&
+ Objects.equals(range(), other.range());
+ }
+
+ @Override
+ public String toString() {
+ return ToString.builder("PresignedUrlGetObjectRequest")
+ .add("PresignedUrl", presignedUrl())
+ .add("Range", range())
+ .build();
+ }
+
+ public interface Builder extends CopyableBuilder {
+ /**
+ * Sets the presigned URL for the S3 object.
+ * @param presignedUrl
+ * @return Returns a reference to this object so that method calls can be chained together.
+ */
+ Builder presignedUrl(URL presignedUrl);
+
+ /**
+ * Specifies the byte range of an object.
+ * @param range The HTTP Range header value (e.g., "bytes=0-1023")
+ * @return Returns a reference to this object so that method calls can be chained together.
+ */
+ Builder range(String range);
+ }
+
+ static final class BuilderImpl implements Builder {
+ private URL presignedUrl;
+ private String range;
+
+ private BuilderImpl() {
+ }
+
+ private BuilderImpl(PresignedUrlGetObjectRequest presignedUrlGetObjectRequest) {
+ presignedUrl(presignedUrlGetObjectRequest.presignedUrl());
+ range(presignedUrlGetObjectRequest.range());
+ }
+
+ @Override
+ public Builder presignedUrl(URL presignedUrl) {
+ this.presignedUrl = presignedUrl;
+ return this;
+ }
+
+ @Override
+ public Builder range(String range) {
+ this.range = range;
+ return this;
+ }
+
+ @Override
+ public PresignedUrlGetObjectRequest build() {
+ Validate.paramNotNull(presignedUrl, "presignedUrl");
+ return new PresignedUrlGetObjectRequest(this);
+ }
+ }
+}
diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/model/PresignedUrlGetObjectRequestWrapperTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/model/PresignedUrlGetObjectRequestWrapperTest.java
new file mode 100644
index 000000000000..8334050fa7c8
--- /dev/null
+++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/model/PresignedUrlGetObjectRequestWrapperTest.java
@@ -0,0 +1,94 @@
+/*
+ * 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.services.s3.internal.presignedurl.model;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.net.URL;
+import java.util.List;
+import java.util.Map;
+import nl.jqno.equalsverifier.EqualsVerifier;
+import org.junit.jupiter.api.Test;
+import software.amazon.awssdk.core.SdkField;
+import software.amazon.awssdk.core.protocol.MarshallLocation;
+
+
+class PresignedUrlGetObjectRequestWrapperTest {
+ @Test
+ void equalsAndHashCode_shouldFollowContract() {
+ EqualsVerifier.forClass(PresignedUrlGetObjectRequestWrapper.class)
+ .withRedefinedSuperclass()
+ .verify();
+ }
+
+ @Test
+ void basicProperties_shouldWork() throws Exception {
+ PresignedUrlGetObjectRequestWrapper request = PresignedUrlGetObjectRequestWrapper.builder()
+ .url(new URL("https://example.com"))
+ .range("bytes=0-100")
+ .build();
+
+ assertThat(request.url()).isEqualTo(new URL("https://example.com"));
+ assertThat(request.range()).isEqualTo("bytes=0-100");
+ }
+
+ @Test
+ void sdkFields_shouldReturnExpectedFields() throws Exception {
+ PresignedUrlGetObjectRequestWrapper request = PresignedUrlGetObjectRequestWrapper.builder()
+ .url(new URL("https://example.com"))
+ .range("bytes=0-100")
+ .build();
+
+ List> fields = request.sdkFields();
+
+ assertThat(fields).hasSize(1);
+ assertThat(fields.get(0).memberName()).isEqualTo("Range");
+ assertThat(fields.get(0).location()).isEqualTo(MarshallLocation.HEADER);
+
+ // Assert that range value is present
+ Object rangeValue = fields.get(0).getValueOrDefault(request);
+ assertThat(rangeValue).isNotNull();
+ assertThat(rangeValue).isEqualTo("bytes=0-100");
+ }
+
+ @Test
+ void sdkFieldNameToField_shouldReturnExpectedMapping() throws Exception {
+ PresignedUrlGetObjectRequestWrapper request = PresignedUrlGetObjectRequestWrapper.builder()
+ .url(new URL("https://example.com"))
+ .build();
+
+ Map> fieldMap = request.sdkFieldNameToField();
+
+ assertThat(fieldMap)
+ .hasSize(1)
+ .containsKey("Range");
+ assertThat(fieldMap.get("Range").memberName()).isEqualTo("Range");
+ }
+
+ @Test
+ void rangeField_shouldMarshalCorrectly() throws Exception {
+ PresignedUrlGetObjectRequestWrapper request = PresignedUrlGetObjectRequestWrapper.builder()
+ .url(new URL("https://example.com"))
+ .range("bytes=0-1023")
+ .build();
+
+ // Test that the SdkField can extract the range value
+ SdkField> rangeField = request.sdkFields().get(0);
+ Object extractedValue = rangeField.getValueOrDefault(request);
+
+ assertThat(extractedValue).isEqualTo("bytes=0-1023");
+ }
+}
diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/presignedurl/model/PresignedUrlGetObjectRequestTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/presignedurl/model/PresignedUrlGetObjectRequestTest.java
new file mode 100644
index 000000000000..14048f2fe1a7
--- /dev/null
+++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/presignedurl/model/PresignedUrlGetObjectRequestTest.java
@@ -0,0 +1,115 @@
+/*
+ * 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.services.s3.presignedurl.model;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.net.URL;
+import nl.jqno.equalsverifier.EqualsVerifier;
+import org.junit.jupiter.api.Test;
+
+class PresignedUrlGetObjectRequestTest {
+
+ @Test
+ void equalsAndHashCode_shouldFollowContract() {
+ EqualsVerifier.forClass(PresignedUrlGetObjectRequest.class)
+ .verify();
+ }
+
+ @Test
+ void builder_shouldCreateRequestWithAllFields() throws Exception {
+ URL url = new URL("https://example.com");
+ PresignedUrlGetObjectRequest request = PresignedUrlGetObjectRequest.builder()
+ .presignedUrl(url)
+ .range("bytes=0-100")
+ .build();
+
+ assertThat(request.presignedUrl()).isEqualTo(url);
+ assertThat(request.range()).isEqualTo("bytes=0-100");
+ }
+
+ @Test
+ void builder_shouldCreateRequestWithOnlyRequiredFields() throws Exception {
+ URL url = new URL("https://example.com");
+ PresignedUrlGetObjectRequest request = PresignedUrlGetObjectRequest.builder()
+ .presignedUrl(url)
+ .build();
+
+ assertThat(request.presignedUrl()).isEqualTo(url);
+ assertThat(request.range()).isNull();
+ }
+
+ @Test
+ void toBuilder_shouldCreateBuilderFromExistingRequest() throws Exception {
+ URL url = new URL("https://example.com");
+ PresignedUrlGetObjectRequest original = PresignedUrlGetObjectRequest.builder()
+ .presignedUrl(url)
+ .range("bytes=0-100")
+ .build();
+
+ PresignedUrlGetObjectRequest copy = original.toBuilder().build();
+
+ assertThat(copy.presignedUrl()).isEqualTo(original.presignedUrl());
+ assertThat(copy.range()).isEqualTo(original.range());
+ }
+
+ @Test
+ void toBuilder_shouldAllowModification() throws Exception {
+ URL url1 = new URL("https://example.com");
+ URL url2 = new URL("https://other.com");
+ PresignedUrlGetObjectRequest original = PresignedUrlGetObjectRequest.builder()
+ .presignedUrl(url1)
+ .range("bytes=0-100")
+ .build();
+
+ PresignedUrlGetObjectRequest modified = original.toBuilder()
+ .presignedUrl(url2)
+ .range("bytes=200-300")
+ .build();
+
+ assertThat(modified.presignedUrl()).isEqualTo(url2);
+ assertThat(modified.range()).isEqualTo("bytes=200-300");
+ // Original unchanged
+ assertThat(original.presignedUrl()).isEqualTo(url1);
+ assertThat(original.range()).isEqualTo("bytes=0-100");
+ }
+
+ @Test
+ void toString_shouldContainActualFieldValues() throws Exception {
+ URL url = new URL("https://example.com");
+ String range = "bytes=0-100";
+
+ PresignedUrlGetObjectRequest request = PresignedUrlGetObjectRequest.builder()
+ .presignedUrl(url)
+ .range(range)
+ .build();
+
+ String result = request.toString();
+
+ assertThat(result)
+ .isNotNull()
+ .isNotEmpty()
+ .contains(request.presignedUrl().toString())
+ .contains(request.range());
+
+ }
+
+ @Test
+ void serializableBuilderClass_shouldReturnCorrectClass() {
+ assertThat(PresignedUrlGetObjectRequest.serializableBuilderClass())
+ .isEqualTo(PresignedUrlGetObjectRequest.BuilderImpl.class);
+ }
+}
From 451a70bed0ff222aa065f48900dfac0dcb0e8d1a Mon Sep 17 00:00:00 2001
From: jencymaryjoseph <35571282+jencymaryjoseph@users.noreply.github.com>
Date: Fri, 4 Jul 2025 10:04:08 -0700
Subject: [PATCH 02/30] Add custom marshaller for pre-signed URL GET operations
(#6231)
* presigned url get object request custom marshaller
* used protocolFactory and fixed sonar cube issues
* removed unused test method, removed overriden method
---
...resignedUrlGetObjectRequestMarshaller.java | 76 ++++++++
...gnedUrlGetObjectRequestMarshallerTest.java | 181 ++++++++++++++++++
2 files changed, 257 insertions(+)
create mode 100644 services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/presignedurl/PresignedUrlGetObjectRequestMarshaller.java
create mode 100644 services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/PresignedUrlGetObjectRequestMarshallerTest.java
diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/presignedurl/PresignedUrlGetObjectRequestMarshaller.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/presignedurl/PresignedUrlGetObjectRequestMarshaller.java
new file mode 100644
index 000000000000..76e1500f2d54
--- /dev/null
+++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/presignedurl/PresignedUrlGetObjectRequestMarshaller.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.services.s3.internal.presignedurl;
+
+import java.net.URI;
+import software.amazon.awssdk.annotations.SdkInternalApi;
+import software.amazon.awssdk.core.exception.SdkClientException;
+import software.amazon.awssdk.core.runtime.transform.Marshaller;
+import software.amazon.awssdk.http.SdkHttpFullRequest;
+import software.amazon.awssdk.http.SdkHttpMethod;
+import software.amazon.awssdk.protocols.core.OperationInfo;
+import software.amazon.awssdk.protocols.core.ProtocolMarshaller;
+import software.amazon.awssdk.protocols.xml.AwsXmlProtocolFactory;
+import software.amazon.awssdk.services.s3.internal.presignedurl.model.PresignedUrlGetObjectRequestWrapper;
+import software.amazon.awssdk.utils.Validate;
+
+/**
+ * {@link PresignedUrlGetObjectRequestWrapper} Marshaller
+ *
+ *
+ * Marshalls presigned URL requests by using the complete URL directly and adding optional Range headers.
+ * Unlike regular S3 marshalers, this preserves all embedded authentication parameters in the presigned URL.
+ *
+ */
+@SdkInternalApi
+public class PresignedUrlGetObjectRequestMarshaller implements Marshaller {
+ private static final OperationInfo SDK_OPERATION_BINDING = OperationInfo.builder()
+ .requestUri("").httpMethod(SdkHttpMethod.GET).hasExplicitPayloadMember(false).hasPayloadMembers(false)
+ .putAdditionalMetadata(AwsXmlProtocolFactory.ROOT_MARSHALL_LOCATION_ATTRIBUTE, null)
+ .putAdditionalMetadata(AwsXmlProtocolFactory.XML_NAMESPACE_ATTRIBUTE, null).build();
+
+ private final AwsXmlProtocolFactory protocolFactory;
+
+ public PresignedUrlGetObjectRequestMarshaller(AwsXmlProtocolFactory protocolFactory) {
+ this.protocolFactory = protocolFactory;
+ }
+
+ /**
+ * Marshalls the presigned URL request into an HTTP GET request.
+ *
+ * @param presignedUrlGetObjectRequestWrapper the request to marshall
+ * @return HTTP request ready for execution
+ * @throws SdkClientException if URL conversion fails
+ */
+ @Override
+ public SdkHttpFullRequest marshall(PresignedUrlGetObjectRequestWrapper presignedUrlGetObjectRequestWrapper) {
+ Validate.paramNotNull(presignedUrlGetObjectRequestWrapper, "presignedUrlGetObjectRequestWrapper");
+ try {
+ ProtocolMarshaller protocolMarshaller = protocolFactory
+ .createProtocolMarshaller(SDK_OPERATION_BINDING);
+ URI presignedUri = presignedUrlGetObjectRequestWrapper.url().toURI();
+
+ return protocolMarshaller.marshall(presignedUrlGetObjectRequestWrapper)
+ .toBuilder()
+ .uri(presignedUri)
+ .build();
+ } catch (Exception e) {
+ throw SdkClientException.builder()
+ .message("Unable to marshall pre-signed URL Request: " + e.getMessage())
+ .cause(e).build();
+ }
+ }
+}
\ No newline at end of file
diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/PresignedUrlGetObjectRequestMarshallerTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/PresignedUrlGetObjectRequestMarshallerTest.java
new file mode 100644
index 000000000000..2311017a29e5
--- /dev/null
+++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/PresignedUrlGetObjectRequestMarshallerTest.java
@@ -0,0 +1,181 @@
+/*
+ * 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.services.s3.internal.presignedurl;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import java.net.URL;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.NullAndEmptySource;
+import org.junit.jupiter.params.provider.ValueSource;
+import software.amazon.awssdk.core.exception.SdkClientException;
+import software.amazon.awssdk.http.SdkHttpFullRequest;
+import software.amazon.awssdk.http.SdkHttpMethod;
+import software.amazon.awssdk.protocols.core.OperationInfo;
+import software.amazon.awssdk.protocols.core.ProtocolMarshaller;
+import software.amazon.awssdk.protocols.xml.AwsXmlProtocolFactory;
+import software.amazon.awssdk.services.s3.internal.presignedurl.model.PresignedUrlGetObjectRequestWrapper;
+
+class PresignedUrlGetObjectRequestMarshallerTest {
+
+ private PresignedUrlGetObjectRequestMarshaller marshaller;
+ private AwsXmlProtocolFactory mockProtocolFactory;
+ private ProtocolMarshaller mockProtocolMarshaller;
+ private URL testUrl;
+
+ @BeforeEach
+ void setUp() throws Exception {
+ mockProtocolFactory = mock(AwsXmlProtocolFactory.class);
+ mockProtocolMarshaller = mock(ProtocolMarshaller.class);
+ when(mockProtocolFactory.createProtocolMarshaller(any(OperationInfo.class)))
+ .thenReturn(mockProtocolMarshaller);
+ marshaller = new PresignedUrlGetObjectRequestMarshaller(mockProtocolFactory);
+
+ testUrl = new URL("https://test-bucket.s3.us-east-1.amazonaws.com/test-key?" +
+ "X-Amz-Date=20231215T000000Z&" +
+ "X-Amz-Signature=example-signature&" +
+ "X-Amz-Algorithm=AWS4-HMAC-SHA256&" +
+ "X-Amz-SignedHeaders=host&" +
+ "X-Amz-Security-Token=xxx&" +
+ "X-Amz-Credential=EXAMPLE12345678901234%2F20231215%2Fus-east-1%2Fs3%2Faws4_request&" +
+ "X-Amz-Expires=3600");
+ }
+
+ @Test
+ void marshall_withBasicRequest_shouldCreateCorrectHttpRequest() throws Exception {
+ // Setup the mock marshaller to return a properly configured request
+ SdkHttpFullRequest baseRequest = SdkHttpFullRequest.builder()
+ .method(SdkHttpMethod.GET)
+ .protocol("https")
+ .host("example.com")
+ .build();
+ when(mockProtocolMarshaller.marshall(any(PresignedUrlGetObjectRequestWrapper.class)))
+ .thenReturn(baseRequest);
+
+ PresignedUrlGetObjectRequestWrapper request = PresignedUrlGetObjectRequestWrapper.builder()
+ .url(testUrl)
+ .build();
+ SdkHttpFullRequest result = marshaller.marshall(request);
+
+ // Verify HTTP method and URI components
+ assertThat(result.method()).isEqualTo(SdkHttpMethod.GET);
+ assertThat(result.getUri())
+ .satisfies(uri -> {
+ assertThat(uri.getScheme()).isEqualTo("https");
+ assertThat(uri.getHost()).isEqualTo("test-bucket.s3.us-east-1.amazonaws.com");
+ assertThat(uri.getPath()).isEqualTo("/test-key");
+ });
+
+ // Verify query parameters are preserved
+ assertThat(result.getUri().getQuery())
+ .contains("X-Amz-Date=20231215T000000Z")
+ .contains("X-Amz-Signature=example-signature")
+ .contains("X-Amz-Algorithm=AWS4-HMAC-SHA256")
+ .contains("X-Amz-SignedHeaders=host")
+ .contains("X-Amz-Security-Token=xxx")
+ .contains("X-Amz-Credential=EXAMPLE12345678901234")
+ .contains("X-Amz-Expires=3600");
+
+ assertThat(result.headers()).doesNotContainKey("Range");
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {
+ "bytes=0-100", // First 101 bytes
+ "bytes=100-", // From byte 100 to end
+ "bytes=-100", // Last 100 bytes
+ "bytes=0-0", // Single byte
+ "bytes=100-200" // Specific range
+ })
+ void marshall_withValidRangeFormats_shouldAddRangeHeader(String rangeValue) throws Exception {
+ // Setup the mock marshaller to return a request with the Range header already set
+ SdkHttpFullRequest baseRequest = SdkHttpFullRequest.builder()
+ .method(SdkHttpMethod.GET)
+ .protocol("https")
+ .host("example.com")
+ .putHeader("Range", rangeValue) // Add the Range header to the mock response
+ .build();
+
+ when(mockProtocolMarshaller.marshall(any(PresignedUrlGetObjectRequestWrapper.class)))
+ .thenReturn(baseRequest);
+
+ PresignedUrlGetObjectRequestWrapper request = PresignedUrlGetObjectRequestWrapper.builder()
+ .url(testUrl)
+ .range(rangeValue)
+ .build();
+
+ SdkHttpFullRequest result = marshaller.marshall(request);
+
+ // Verify the Range header is preserved
+ assertThat(result.headers())
+ .containsKey("Range")
+ .satisfies(headers -> assertThat(headers.get("Range")).contains(rangeValue));
+ }
+
+ @ParameterizedTest
+ @NullAndEmptySource
+ void marshall_withNullOrEmptyRange_shouldNotAddRangeHeader(String rangeValue) throws Exception {
+ // Setup the mock marshaller to return a properly configured request
+ SdkHttpFullRequest baseRequest = SdkHttpFullRequest.builder()
+ .method(SdkHttpMethod.GET)
+ .protocol("https")
+ .host("example.com")
+ .build();
+ when(mockProtocolMarshaller.marshall(any(PresignedUrlGetObjectRequestWrapper.class)))
+ .thenReturn(baseRequest);
+
+ PresignedUrlGetObjectRequestWrapper request = PresignedUrlGetObjectRequestWrapper.builder()
+ .url(testUrl)
+ .range(rangeValue)
+ .build();
+ SdkHttpFullRequest result = marshaller.marshall(request);
+
+ assertThat(result.headers()).doesNotContainKey("Range");
+ }
+
+ @Test
+ void marshall_withNullRequest_shouldThrowException() {
+ assertThatThrownBy(() -> marshaller.marshall(null))
+ .isInstanceOf(NullPointerException.class)
+ .hasMessageContaining("presignedUrlGetObjectRequestWrapper must not be null");
+ }
+
+ @Test
+ void marshall_withMalformedUrl_shouldThrowSdkClientException() throws Exception {
+ // Setup the mock marshaller to return a properly configured request
+ SdkHttpFullRequest baseRequest = SdkHttpFullRequest.builder()
+ .method(SdkHttpMethod.GET)
+ .protocol("https")
+ .host("example.com")
+ .build();
+ when(mockProtocolMarshaller.marshall(any(PresignedUrlGetObjectRequestWrapper.class)))
+ .thenReturn(baseRequest);
+
+ URL malformedUrl = new URL("https", "test-bucket.s3.us-east-1.amazonaws.com", -1, "/test key with spaces");
+ PresignedUrlGetObjectRequestWrapper request = PresignedUrlGetObjectRequestWrapper.builder()
+ .url(malformedUrl)
+ .build();
+
+ assertThatThrownBy(() -> marshaller.marshall(request))
+ .isInstanceOf(SdkClientException.class)
+ .hasMessageContaining("Unable to marshall pre-signed URL Request");
+ }
+}
\ No newline at end of file
From 563237b8e1fa878718b78a7f36a15f6604f2d03f Mon Sep 17 00:00:00 2001
From: jencymaryjoseph <35571282+jencymaryjoseph@users.noreply.github.com>
Date: Thu, 10 Jul 2025 11:02:33 -0700
Subject: [PATCH 03/30] Add DefaultPresignedUrlManager getObject implementation
for S3 pre-signed URL (#6241)
* getobject implementation and tests
* corrected the getObject params in test:
* fixed build failure-dependency issue with Retrystrategy in test
* fixed sonarcube issues
* addressed sonarcube issue
* edited javadocs, added invalidurl test
---
.../DefaultPresignedUrlManager.java | 122 ++++++
.../DefaultPresignedUrlManagerTest.java | 378 ++++++++++++++++++
2 files changed, 500 insertions(+)
create mode 100644 services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/presignedurl/DefaultPresignedUrlManager.java
create mode 100644 services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/DefaultPresignedUrlManagerTest.java
diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/presignedurl/DefaultPresignedUrlManager.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/presignedurl/DefaultPresignedUrlManager.java
new file mode 100644
index 000000000000..3527b4d830d4
--- /dev/null
+++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/presignedurl/DefaultPresignedUrlManager.java
@@ -0,0 +1,122 @@
+/*
+ * 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.services.s3.internal.presignedurl;
+
+import static software.amazon.awssdk.core.client.config.SdkClientOption.SIGNER_OVERRIDDEN;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import software.amazon.awssdk.annotations.SdkInternalApi;
+import software.amazon.awssdk.awscore.exception.AwsServiceException;
+import software.amazon.awssdk.awscore.internal.AwsProtocolMetadata;
+import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption;
+import software.amazon.awssdk.core.client.config.SdkClientConfiguration;
+import software.amazon.awssdk.core.client.config.SdkClientOption;
+import software.amazon.awssdk.core.client.handler.ClientExecutionParams;
+import software.amazon.awssdk.core.client.handler.SyncClientHandler;
+import software.amazon.awssdk.core.exception.SdkClientException;
+import software.amazon.awssdk.core.http.HttpResponseHandler;
+import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute;
+import software.amazon.awssdk.core.metrics.CoreMetric;
+import software.amazon.awssdk.core.signer.NoOpSigner;
+import software.amazon.awssdk.core.sync.ResponseTransformer;
+import software.amazon.awssdk.metrics.MetricCollector;
+import software.amazon.awssdk.metrics.MetricPublisher;
+import software.amazon.awssdk.metrics.NoOpMetricCollector;
+import software.amazon.awssdk.protocols.xml.AwsS3ProtocolFactory;
+import software.amazon.awssdk.protocols.xml.XmlOperationMetadata;
+import software.amazon.awssdk.services.s3.internal.presignedurl.model.PresignedUrlGetObjectRequestWrapper;
+import software.amazon.awssdk.services.s3.model.GetObjectResponse;
+import software.amazon.awssdk.services.s3.model.InvalidObjectStateException;
+import software.amazon.awssdk.services.s3.model.NoSuchKeyException;
+import software.amazon.awssdk.services.s3.model.S3Exception;
+import software.amazon.awssdk.services.s3.presignedurl.PresignedUrlManager;
+import software.amazon.awssdk.services.s3.presignedurl.model.PresignedUrlGetObjectRequest;
+
+/**
+ * Default implementation of {@link PresignedUrlManager} for executing S3 operations using presigned URLs.
+ */
+@SdkInternalApi
+public final class DefaultPresignedUrlManager implements PresignedUrlManager {
+
+ private final SyncClientHandler clientHandler;
+ private final AwsS3ProtocolFactory protocolFactory;
+ private final SdkClientConfiguration clientConfiguration;
+ private final AwsProtocolMetadata protocolMetadata;
+
+ public DefaultPresignedUrlManager(SyncClientHandler clientHandler,
+ AwsS3ProtocolFactory protocolFactory,
+ SdkClientConfiguration clientConfiguration,
+ AwsProtocolMetadata protocolMetadata) {
+ this.clientHandler = clientHandler;
+ this.protocolFactory = protocolFactory;
+ this.clientConfiguration = clientConfiguration;
+ this.protocolMetadata = protocolMetadata;
+ }
+
+ @Override
+ public ReturnT getObject(PresignedUrlGetObjectRequest presignedUrlGetObjectRequest,
+ ResponseTransformer responseTransformer)
+ throws NoSuchKeyException, InvalidObjectStateException,
+ AwsServiceException, SdkClientException, S3Exception {
+
+ HttpResponseHandler responseHandler = protocolFactory.createResponseHandler(
+ GetObjectResponse::builder, new XmlOperationMetadata().withHasStreamingSuccessResponse(true));
+
+ HttpResponseHandler errorResponseHandler = protocolFactory.createErrorResponseHandler();
+
+ PresignedUrlGetObjectRequestWrapper internalRequest = PresignedUrlGetObjectRequestWrapper.builder()
+ .url(presignedUrlGetObjectRequest.presignedUrl())
+ .range(presignedUrlGetObjectRequest.range())
+ .build();
+
+ SdkClientConfiguration updatedClientConfiguration = updateSdkClientConfiguration(this.clientConfiguration);
+ List metricPublishers = Optional.ofNullable(
+ updatedClientConfiguration.option(SdkClientOption.METRIC_PUBLISHERS))
+ .orElse(Collections.emptyList());
+ MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ?
+ NoOpMetricCollector.create() : MetricCollector.create("ApiCall");
+ try {
+ apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "S3");
+ //TODO: Discuss if we need to change OPERATION_NAME as part of Surface API Review
+ apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "GetObject");
+
+ return clientHandler.execute(
+ new ClientExecutionParams()
+ .withOperationName("PresignedUrlGetObject")
+ .withProtocolMetadata(protocolMetadata)
+ .withResponseHandler(responseHandler)
+ .withErrorResponseHandler(errorResponseHandler)
+ .withRequestConfiguration(updatedClientConfiguration)
+ .withInput(internalRequest)
+ .withMetricCollector(apiCallMetricCollector)
+ // TODO: Deprecate IS_DISCOVERED_ENDPOINT, use new SKIP_ENDPOINT_RESOLUTION for better semantics
+ .putExecutionAttribute(SdkInternalExecutionAttribute.IS_DISCOVERED_ENDPOINT, true)
+ .withMarshaller(new PresignedUrlGetObjectRequestMarshaller(protocolFactory)), responseTransformer);
+ } finally {
+ metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect()));
+ }
+ }
+
+ private SdkClientConfiguration updateSdkClientConfiguration(SdkClientConfiguration clientConfiguration) {
+ SdkClientConfiguration.Builder configBuilder = clientConfiguration.toBuilder();
+ configBuilder.option(SdkAdvancedClientOption.SIGNER, new NoOpSigner());
+ configBuilder.option(SIGNER_OVERRIDDEN, true);
+ return configBuilder.build();
+ }
+
+}
diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/DefaultPresignedUrlManagerTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/DefaultPresignedUrlManagerTest.java
new file mode 100644
index 000000000000..ff6266689927
--- /dev/null
+++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/DefaultPresignedUrlManagerTest.java
@@ -0,0 +1,378 @@
+/*
+ * 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.services.s3.internal.presignedurl;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.ArgumentCaptor;
+import software.amazon.awssdk.awscore.retry.AwsRetryStrategy;
+import software.amazon.awssdk.core.exception.SdkClientException;
+import software.amazon.awssdk.metrics.MetricCollection;
+import software.amazon.awssdk.metrics.MetricPublisher;
+import software.amazon.awssdk.core.metrics.CoreMetric;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.any;
+import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider;
+import software.amazon.awssdk.awscore.client.config.AwsAdvancedClientOption;
+import software.amazon.awssdk.awscore.client.config.AwsClientOption;
+import software.amazon.awssdk.awscore.client.handler.AwsSyncClientHandler;
+import software.amazon.awssdk.awscore.internal.AwsProtocolMetadata;
+import software.amazon.awssdk.awscore.internal.AwsServiceProtocol;
+import software.amazon.awssdk.core.ResponseInputStream;
+import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption;
+import software.amazon.awssdk.core.client.config.SdkClientConfiguration;
+import software.amazon.awssdk.core.client.config.SdkClientOption;
+import software.amazon.awssdk.core.client.handler.SyncClientHandler;
+import software.amazon.awssdk.core.sync.ResponseTransformer;
+import software.amazon.awssdk.http.AbortableInputStream;
+import software.amazon.awssdk.http.HttpExecuteResponse;
+import software.amazon.awssdk.http.SdkHttpFullResponse;
+import software.amazon.awssdk.http.SdkHttpMethod;
+import software.amazon.awssdk.http.SdkHttpRequest;
+import software.amazon.awssdk.protocols.core.ExceptionMetadata;
+import software.amazon.awssdk.protocols.xml.AwsS3ProtocolFactory;
+import software.amazon.awssdk.regions.Region;
+import software.amazon.awssdk.services.s3.model.GetObjectResponse;
+import software.amazon.awssdk.services.s3.model.InvalidObjectStateException;
+import software.amazon.awssdk.services.s3.model.NoSuchKeyException;
+import software.amazon.awssdk.services.s3.model.S3Exception;
+import software.amazon.awssdk.services.s3.presignedurl.model.PresignedUrlGetObjectRequest;
+import software.amazon.awssdk.testutils.service.http.MockSyncHttpClient;
+import software.amazon.awssdk.utils.IoUtils;
+
+/**
+ * Tests for {@link DefaultPresignedUrlManager} using MockSyncHttpClient to verify HTTP interactions.
+ */
+ class DefaultPresignedUrlManagerTest {
+
+ private static final String TEST_CONTENT = "test-content";
+ private static final URI DEFAULT_ENDPOINT = URI.create("https://defaultendpoint.com");
+ private static final String TEST_URL = "https://test-bucket.s3.us-east-1.amazonaws.com/test-key?" +
+ "X-Amz-Date=20250707T000000Z&" +
+ "X-Amz-Signature=test-signature-value&" +
+ "X-Amz-Algorithm=AWS4-HMAC-SHA256&" +
+ "X-Amz-SignedHeaders=host&" +
+ "X-Amz-Security-Token=test-session-token&" +
+ "X-Amz-Credential=AKIAIOSFODNN7EXAMPLE%2F20250707%2Fus-east-1%2Fs3%2Faws4_request&" +
+ "X-Amz-Expires=86400";
+ private MockSyncHttpClient mockHttpClient;
+ private DefaultPresignedUrlManager presignedUrlManager;
+ private URL testPresignedUrl;
+ private PresignedUrlGetObjectRequest testRequest;
+ private AwsProtocolMetadata protocolMetadata;
+ private AwsS3ProtocolFactory protocolFactory;
+ private SyncClientHandler clientHandler;
+ private SdkClientConfiguration clientConfiguration;
+
+ @BeforeEach
+ void setUp() throws Exception {
+ mockHttpClient = new MockSyncHttpClient();
+ testPresignedUrl = new URL(TEST_URL);
+ testRequest = PresignedUrlGetObjectRequest.builder()
+ .presignedUrl(testPresignedUrl)
+ .build();
+
+ clientConfiguration = getDefaultSdkConfigs();
+ protocolMetadata = AwsProtocolMetadata.builder()
+ .serviceProtocol(AwsServiceProtocol.REST_XML)
+ .build();
+ protocolFactory = initProtocolFactory(clientConfiguration);
+ clientHandler = new AwsSyncClientHandler(clientConfiguration);
+
+ presignedUrlManager = new DefaultPresignedUrlManager(
+ clientHandler, protocolFactory, clientConfiguration, protocolMetadata);
+ }
+
+ private static Stream httpResponseTestCases() {
+ return Stream.of(
+ Arguments.of(
+ "Success response",
+ createSuccessResponse(),
+ true,
+ null
+ ),
+ Arguments.of(
+ "404 Not Found response",
+ createErrorResponse(404, "NoSuchKey", "The specified key does not exist."),
+ false,
+ NoSuchKeyException.class
+ ),
+ Arguments.of(
+ "403 Invalid Object State response",
+ createErrorResponse(403, "InvalidObjectState", "The operation is not valid for the object's storage class."),
+ false,
+ InvalidObjectStateException.class
+ ),
+ Arguments.of(
+ "Generic error response",
+ createErrorResponse(500, "InternalError", "We encountered an internal error. Please try again."),
+ false,
+ S3Exception.class
+ )
+ );
+ }
+
+ private static Stream requestConfigurationTestCases() {
+ return Stream.of(
+ Arguments.of(
+ "Basic request",
+ (Consumer) builder ->
+ builder.presignedUrl(createTestUrl()),
+ null
+ ),
+ Arguments.of(
+ "Request with range header",
+ (Consumer) builder ->
+ builder.presignedUrl(createTestUrl()).range("bytes=0-1024"),
+ "bytes=0-1024"
+ )
+ );
+ }
+
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("httpResponseTestCases")
+ void given_PresignedUrlManager_when_GetObjectWithDifferentHttpResponses_then_ShouldHandleSuccessAndErrorsCorrectly(String testName,
+ HttpExecuteResponse response,
+ boolean expectSuccess,
+ Class extends Exception> expectedExceptionType) {
+ mockHttpClient.stubNextResponse(response);
+ if (expectSuccess) {
+ assertSuccessfulGetObject(testRequest);
+ } else {
+ assertThatThrownBy(() -> presignedUrlManager.getObject(testRequest, ResponseTransformer.toInputStream()))
+ .isInstanceOf(expectedExceptionType);
+ }
+ }
+
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("requestConfigurationTestCases")
+ void given_PresignedUrlManager_when_GetObjectWithDifferentRequestConfigurations_then_ShouldSetCorrectHeaders(String testName,
+ Consumer requestCustomizer,
+ String expectedRangeHeader) throws IOException {
+ mockHttpClient.stubNextResponse(createSuccessResponse());
+ PresignedUrlGetObjectRequest.Builder builder = PresignedUrlGetObjectRequest.builder();
+ requestCustomizer.accept(builder);
+ PresignedUrlGetObjectRequest request = builder.build();
+ ResponseInputStream result = presignedUrlManager.getObject(request, ResponseTransformer.toInputStream());
+ assertThat(result).isNotNull();
+ String content = IoUtils.toUtf8String(result);
+ assertThat(content).isEqualTo(TEST_CONTENT);
+ SdkHttpRequest lastRequest = mockHttpClient.getLastRequest();
+ assertThat(lastRequest.method()).isEqualTo(SdkHttpMethod.GET);
+ if (expectedRangeHeader != null) {
+ assertThat(lastRequest.firstMatchingHeader("Range")).isPresent()
+ .contains(expectedRangeHeader);
+ }
+ }
+
+ private static Stream additionalTestCases() {
+ return Stream.of(
+ Arguments.of(
+ "Custom transformer test",
+ "CUSTOM_TRANSFORMER"
+ ),
+ Arguments.of(
+ "Resolved endpoint verification",
+ "ENDPOINT_VERIFICATION"
+ ),
+ Arguments.of(
+ "Metrics collection test",
+ "METRICS_COLLECTION"
+ )
+ );
+ }
+
+ private static Stream invalidUrlTestCases() {
+ return Stream.of(
+ Arguments.of(
+ "URL with spaces in path",
+ "https://test-bucket.s3.us-east-1.amazonaws.com/test key with spaces"
+ ),
+ Arguments.of(
+ "URL with invalid characters",
+ "https://test-bucket.s3.us-east-1.amazonaws.com/test<>key"
+ ),
+ Arguments.of(
+ "Malformed URL",
+ "not-a-valid-url"
+ )
+ );
+ }
+
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("additionalTestCases")
+ void given_PresignedUrlManager_when_ExecutingDifferentScenarios_then_ShouldBehaveCorrectly(String testName,
+ String testType) {
+ switch (testType) {
+ case "CUSTOM_TRANSFORMER":
+ mockHttpClient.stubNextResponse(createSuccessResponse());
+ ResponseTransformer transformer =
+ (response, inputStream) -> IoUtils.toUtf8String(inputStream);
+ String result = presignedUrlManager.getObject(testRequest, transformer);
+ assertThat(result).isEqualTo(TEST_CONTENT);
+ break;
+
+ case "ENDPOINT_VERIFICATION":
+ mockHttpClient.stubNextResponse(createSuccessResponse());
+ presignedUrlManager.getObject(testRequest, ResponseTransformer.toInputStream());
+ SdkHttpRequest lastRequest = mockHttpClient.getLastRequest();
+ assertThat(lastRequest.getUri().toString()).startsWith(testPresignedUrl.toString().split("\\?")[0]);
+ String presignedUrlQuery = testPresignedUrl.getQuery();
+ for (String param : presignedUrlQuery.split("&")) {
+ String[] keyValue = param.split("=", 2);
+ if (keyValue.length == 2) {
+ String key = keyValue[0];
+ String value = keyValue[1];
+ assertThat(lastRequest.getUri().toString()).contains(key + "=" + value);
+ }
+ }
+ break;
+
+ case "METRICS_COLLECTION":
+ MetricPublisher mockPublisher = mock(MetricPublisher.class);
+ SdkClientConfiguration clientConfigWithMetrics = getDefaultSdkConfigs().toBuilder()
+ .option(SdkClientOption.METRIC_PUBLISHERS, Collections.singletonList(mockPublisher))
+ .build();
+ DefaultPresignedUrlManager managerWithMetrics = new DefaultPresignedUrlManager(
+ clientHandler, protocolFactory, clientConfigWithMetrics, protocolMetadata);
+ mockHttpClient.stubNextResponse(createSuccessResponse());
+ managerWithMetrics.getObject(testRequest, ResponseTransformer.toInputStream());
+ verify(mockPublisher, atLeastOnce()).publish(any(MetricCollection.class));
+ ArgumentCaptor metricsCaptor = ArgumentCaptor.forClass(MetricCollection.class);
+ verify(mockPublisher).publish(metricsCaptor.capture());
+ MetricCollection capturedMetrics = metricsCaptor.getValue();
+ assertThat(capturedMetrics.metricValues(CoreMetric.SERVICE_ID)).contains("S3");
+ assertThat(capturedMetrics.metricValues(CoreMetric.OPERATION_NAME)).contains("GetObject");
+ break;
+ }
+ }
+
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("invalidUrlTestCases")
+ void given_PresignedUrlManager_when_GetObjectWithInvalidUrl_then_ShouldThrowException(String testName, String invalidUrlString) {
+ assertThatThrownBy(() -> {
+ URL invalidUrl = new URL(invalidUrlString);
+ PresignedUrlGetObjectRequest invalidRequest = PresignedUrlGetObjectRequest.builder()
+ .presignedUrl(invalidUrl)
+ .build();
+ presignedUrlManager.getObject(invalidRequest, ResponseTransformer.toInputStream());
+ }).satisfiesAnyOf(
+ ex -> assertThat(ex).isInstanceOf(java.net.MalformedURLException.class),
+ ex -> assertThat(ex).isInstanceOf(SdkClientException.class)
+ );
+ }
+
+ private SdkClientConfiguration getDefaultSdkConfigs() {
+ return SdkClientConfiguration.builder()
+ .option(SdkClientOption.SYNC_HTTP_CLIENT, mockHttpClient)
+ .option(SdkClientOption.ADDITIONAL_HTTP_HEADERS, Collections.emptyMap())
+ .option(SdkClientOption.EXECUTION_INTERCEPTORS, Collections.emptyList())
+ .option(SdkClientOption.RETRY_STRATEGY, AwsRetryStrategy.doNotRetry())
+ .option(SdkAdvancedClientOption.USER_AGENT_PREFIX, "")
+ .option(SdkAdvancedClientOption.USER_AGENT_SUFFIX, "")
+ .option(SdkClientOption.CRC32_FROM_COMPRESSED_DATA_ENABLED, false)
+ .option(AwsClientOption.CREDENTIALS_IDENTITY_PROVIDER, AnonymousCredentialsProvider.create())
+ .option(AwsClientOption.AWS_REGION, Region.US_EAST_2)
+ .option(AwsClientOption.SIGNING_REGION, Region.US_EAST_2)
+ .option(AwsClientOption.SERVICE_SIGNING_NAME, Region.AP_EAST_2.toString())
+ .option(AwsAdvancedClientOption.ENABLE_DEFAULT_REGION_DETECTION, false)
+ .option(SdkClientOption.CLIENT_ENDPOINT_PROVIDER,
+ software.amazon.awssdk.core.ClientEndpointProvider.forEndpointOverride(DEFAULT_ENDPOINT))
+ .build();
+ }
+
+ private AwsS3ProtocolFactory initProtocolFactory(SdkClientConfiguration configuration) {
+ return AwsS3ProtocolFactory.builder()
+ .registerModeledException(
+ ExceptionMetadata.builder().errorCode("NoSuchKey")
+ .exceptionBuilderSupplier(NoSuchKeyException::builder)
+ .httpStatusCode(404).build())
+ .registerModeledException(
+ ExceptionMetadata.builder().errorCode("InvalidObjectState")
+ .exceptionBuilderSupplier(InvalidObjectStateException::builder)
+ .httpStatusCode(403).build())
+ .clientConfiguration(configuration)
+ .defaultServiceExceptionSupplier(S3Exception::builder)
+ .build();
+ }
+
+ private static URL createTestUrl() {
+ try {
+ return new URL(TEST_URL);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void assertSuccessfulGetObject(PresignedUrlGetObjectRequest request) {
+ try {
+ ResponseInputStream result = presignedUrlManager.getObject(request, ResponseTransformer.toInputStream());
+ assertThat(result).isNotNull();
+ String content = IoUtils.toUtf8String(result);
+ assertThat(content).isEqualTo(TEST_CONTENT);
+
+ SdkHttpRequest lastRequest = mockHttpClient.getLastRequest();
+ assertThat(lastRequest.method()).isEqualTo(SdkHttpMethod.GET);
+ assertThat(lastRequest.getUri().toString()).contains("test-bucket.s3.us-east-1.amazonaws.com/test-key");
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ private static HttpExecuteResponse createSuccessResponse() {
+ SdkHttpFullResponse httpResponse = SdkHttpFullResponse.builder()
+ .statusCode(200)
+ .putHeader("Content-Length", "12")
+ .putHeader("ETag", "\"test-etag\"")
+ .putHeader("Content-Type", "text/plain")
+ .build();
+ return HttpExecuteResponse.builder()
+ .response(httpResponse)
+ .responseBody(AbortableInputStream.create(
+ new ByteArrayInputStream(TEST_CONTENT.getBytes(StandardCharsets.UTF_8))))
+ .build();
+ }
+
+ private static HttpExecuteResponse createErrorResponse(int statusCode, String errorCode, String errorMessage) {
+ String errorContent = String.format(
+ "%s%s ",
+ errorCode, errorMessage);
+ SdkHttpFullResponse httpResponse = SdkHttpFullResponse.builder()
+ .statusCode(statusCode)
+ .putHeader("x-amz-request-id", "test-request-id")
+ .putHeader("x-amz-id-2", "test-extended-request-id")
+ .build();
+ return HttpExecuteResponse.builder()
+ .response(httpResponse)
+ .responseBody(AbortableInputStream.create(
+ new ByteArrayInputStream(errorContent.getBytes(StandardCharsets.UTF_8))))
+ .build();
+ }
+}
+
From ba96aaa0dfa9ec0bee5fda555fa412d141563e8e Mon Sep 17 00:00:00 2001
From: jencymaryjoseph <35571282+jencymaryjoseph@users.noreply.github.com>
Date: Wed, 16 Jul 2025 11:52:22 -0700
Subject: [PATCH 04/30] Add AsyncPresignedUrlManager for S3 GetObject
operations with presigned URLs (#6255)
* async presignedurlmanager api and implementation
* Refactor DefaultAsyncPresignedUrlManager: move config to constructor, declare variables inline
* change operation name
---
.../DefaultAsyncPresignedUrlManager.java | 154 ++++++++
.../AsyncPresignedUrlManager.java | 81 ++++
.../DefaultAsyncPresignedUrlManagerTest.java | 373 ++++++++++++++++++
3 files changed, 608 insertions(+)
create mode 100644 services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/presignedurl/DefaultAsyncPresignedUrlManager.java
create mode 100644 services/s3/src/main/java/software/amazon/awssdk/services/s3/presignedurl/AsyncPresignedUrlManager.java
create mode 100644 services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/DefaultAsyncPresignedUrlManagerTest.java
diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/presignedurl/DefaultAsyncPresignedUrlManager.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/presignedurl/DefaultAsyncPresignedUrlManager.java
new file mode 100644
index 000000000000..75c4e581befb
--- /dev/null
+++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/presignedurl/DefaultAsyncPresignedUrlManager.java
@@ -0,0 +1,154 @@
+/*
+ * 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.services.s3.internal.presignedurl;
+
+import static software.amazon.awssdk.core.client.config.SdkClientOption.SIGNER_OVERRIDDEN;
+import static software.amazon.awssdk.utils.FunctionalUtils.runAndLogError;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import software.amazon.awssdk.annotations.SdkInternalApi;
+import software.amazon.awssdk.awscore.exception.AwsServiceException;
+import software.amazon.awssdk.awscore.internal.AwsProtocolMetadata;
+import software.amazon.awssdk.core.async.AsyncResponseTransformer;
+import software.amazon.awssdk.core.async.AsyncResponseTransformerUtils;
+import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption;
+import software.amazon.awssdk.core.client.config.SdkClientConfiguration;
+import software.amazon.awssdk.core.client.config.SdkClientOption;
+import software.amazon.awssdk.core.client.handler.AsyncClientHandler;
+import software.amazon.awssdk.core.client.handler.ClientExecutionParams;
+import software.amazon.awssdk.core.exception.SdkClientException;
+import software.amazon.awssdk.core.http.HttpResponseHandler;
+import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute;
+import software.amazon.awssdk.core.metrics.CoreMetric;
+import software.amazon.awssdk.core.signer.NoOpSigner;
+import software.amazon.awssdk.metrics.MetricCollector;
+import software.amazon.awssdk.metrics.MetricPublisher;
+import software.amazon.awssdk.metrics.NoOpMetricCollector;
+import software.amazon.awssdk.protocols.xml.AwsS3ProtocolFactory;
+import software.amazon.awssdk.protocols.xml.XmlOperationMetadata;
+import software.amazon.awssdk.services.s3.internal.presignedurl.model.PresignedUrlGetObjectRequestWrapper;
+import software.amazon.awssdk.services.s3.model.GetObjectResponse;
+import software.amazon.awssdk.services.s3.model.InvalidObjectStateException;
+import software.amazon.awssdk.services.s3.model.NoSuchKeyException;
+import software.amazon.awssdk.services.s3.model.S3Exception;
+import software.amazon.awssdk.services.s3.presignedurl.AsyncPresignedUrlManager;
+import software.amazon.awssdk.services.s3.presignedurl.model.PresignedUrlGetObjectRequest;
+import software.amazon.awssdk.utils.CompletableFutureUtils;
+import software.amazon.awssdk.utils.Pair;
+
+/**
+ * Default implementation of {@link AsyncPresignedUrlManager} for executing S3 operations asynchronously using presigned URLs.
+ */
+@SdkInternalApi
+public final class DefaultAsyncPresignedUrlManager implements AsyncPresignedUrlManager {
+ private static final Logger log = LoggerFactory.getLogger(DefaultAsyncPresignedUrlManager.class);
+
+ private final AsyncClientHandler clientHandler;
+ private final AwsS3ProtocolFactory protocolFactory;
+ private final SdkClientConfiguration clientConfiguration;
+ private final List metricPublishers;
+ private final AwsProtocolMetadata protocolMetadata;
+
+ public DefaultAsyncPresignedUrlManager(AsyncClientHandler clientHandler,
+ AwsS3ProtocolFactory protocolFactory,
+ SdkClientConfiguration clientConfiguration,
+ AwsProtocolMetadata protocolMetadata) {
+ this.clientHandler = clientHandler;
+ this.protocolFactory = protocolFactory;
+ this.protocolMetadata = protocolMetadata;
+ this.clientConfiguration = updateSdkClientConfiguration(clientConfiguration);
+ this.metricPublishers = Optional.ofNullable(
+ this.clientConfiguration.option(SdkClientOption.METRIC_PUBLISHERS))
+ .orElse(Collections.emptyList());
+ }
+
+ @Override
+ public CompletableFuture getObject(
+ PresignedUrlGetObjectRequest presignedUrlGetObjectRequest,
+ AsyncResponseTransformer asyncResponseTransformer)
+ throws NoSuchKeyException, InvalidObjectStateException,
+ AwsServiceException, SdkClientException, S3Exception {
+
+ PresignedUrlGetObjectRequestWrapper internalRequest = PresignedUrlGetObjectRequestWrapper.builder()
+ .url(presignedUrlGetObjectRequest.presignedUrl())
+ .range(presignedUrlGetObjectRequest.range())
+ .build();
+
+ MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ?
+ NoOpMetricCollector.create() : MetricCollector.create("ApiCall");
+
+ try {
+ apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "S3");
+ //TODO: Discuss if we need to change OPERATION_NAME as part of Surface API Review
+ apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "GetObject");
+
+ Pair, CompletableFuture> pair =
+ AsyncResponseTransformerUtils.wrapWithEndOfStreamFuture(asyncResponseTransformer);
+ AsyncResponseTransformer finalAsyncResponseTransformer = pair.left();
+ asyncResponseTransformer = finalAsyncResponseTransformer;
+ CompletableFuture endOfStreamFuture = pair.right();
+
+ HttpResponseHandler responseHandler = protocolFactory.createResponseHandler(
+ GetObjectResponse::builder, new XmlOperationMetadata().withHasStreamingSuccessResponse(true));
+
+ HttpResponseHandler errorResponseHandler = protocolFactory.createErrorResponseHandler();
+
+ CompletableFuture executeFuture = clientHandler.execute(
+ new ClientExecutionParams()
+ .withOperationName("GetObject")
+ .withProtocolMetadata(protocolMetadata)
+ .withResponseHandler(responseHandler)
+ .withErrorResponseHandler(errorResponseHandler)
+ .withRequestConfiguration(clientConfiguration)
+ .withInput(internalRequest)
+ .withMetricCollector(apiCallMetricCollector)
+ // TODO: Deprecate IS_DISCOVERED_ENDPOINT, use new SKIP_ENDPOINT_RESOLUTION for better semantics
+ .putExecutionAttribute(SdkInternalExecutionAttribute.IS_DISCOVERED_ENDPOINT, true)
+ .withMarshaller(new PresignedUrlGetObjectRequestMarshaller(protocolFactory)),
+ asyncResponseTransformer);
+
+ CompletableFuture whenCompleteFuture = executeFuture.whenComplete((r, e) -> {
+ if (e != null) {
+ runAndLogError(log, "Exception thrown in exceptionOccurred callback, ignoring",
+ () -> finalAsyncResponseTransformer.exceptionOccurred(e));
+ }
+ endOfStreamFuture.whenComplete((r2, e2) -> {
+ metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect()));
+ });
+ });
+ return CompletableFutureUtils.forwardExceptionTo(whenCompleteFuture, executeFuture);
+ } catch (Throwable t) {
+ AsyncResponseTransformer finalAsyncResponseTransformer = asyncResponseTransformer;
+ runAndLogError(log, "Exception thrown in exceptionOccurred callback, ignoring",
+ () -> finalAsyncResponseTransformer.exceptionOccurred(t));
+ metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect()));
+ return CompletableFutureUtils.failedFuture(t);
+ }
+ }
+
+ private SdkClientConfiguration updateSdkClientConfiguration(SdkClientConfiguration clientConfiguration) {
+ SdkClientConfiguration.Builder configBuilder = clientConfiguration.toBuilder();
+ configBuilder.option(SdkAdvancedClientOption.SIGNER, new NoOpSigner());
+ configBuilder.option(SIGNER_OVERRIDDEN, true);
+ return configBuilder.build();
+ }
+
+}
diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/presignedurl/AsyncPresignedUrlManager.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/presignedurl/AsyncPresignedUrlManager.java
new file mode 100644
index 000000000000..6342213a679a
--- /dev/null
+++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/presignedurl/AsyncPresignedUrlManager.java
@@ -0,0 +1,81 @@
+/*
+ * 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.services.s3.presignedurl;
+
+import java.util.concurrent.CompletableFuture;
+import software.amazon.awssdk.annotations.SdkPublicApi;
+import software.amazon.awssdk.core.async.AsyncResponseTransformer;
+import software.amazon.awssdk.core.exception.SdkClientException;
+import software.amazon.awssdk.services.s3.S3AsyncClient;
+import software.amazon.awssdk.services.s3.model.GetObjectResponse;
+import software.amazon.awssdk.services.s3.model.InvalidObjectStateException;
+import software.amazon.awssdk.services.s3.model.NoSuchKeyException;
+import software.amazon.awssdk.services.s3.model.S3Exception;
+import software.amazon.awssdk.services.s3.presignedurl.model.PresignedUrlGetObjectRequest;
+
+/**
+ * Interface for executing S3 operations asynchronously using presigned URLs. This can be accessed using
+ * {@link S3AsyncClient#presignedUrlManager()}.
+ */
+@SdkPublicApi
+public interface AsyncPresignedUrlManager {
+ /**
+ *
+ * Downloads an S3 object asynchronously using a presigned URL.
+ *
+ *
+ * This operation uses a presigned URL that contains all necessary authentication information, eliminating the
+ * need for AWS credentials at request time. The presigned URL must be valid and not expired.
+ *
+ *
+ * Range Requests
+ *
+ *
+ * Supports partial object downloads using HTTP Range headers. Specify the range parameter
+ * in the request to download only a portion of the object (e.g., "bytes=0-1023").
+ *
+ *
+ *
+ *
+ * @param request The presigned URL request containing the URL and optional range parameters
+ * @param responseTransformer Transforms the response to the desired return type. See
+ * {@link software.amazon.awssdk.core.async.AsyncResponseTransformer} for pre-built
+ * implementations like downloading to a file or converting to bytes.
+ * @param The type of the transformed response
+ * @return A {@link CompletableFuture} containing the transformed result of the AsyncResponseTransformer
+ * @throws software.amazon.awssdk.services.s3.model.NoSuchKeyException The specified object does not exist
+ * @throws software.amazon.awssdk.services.s3.model.InvalidObjectStateException Object is archived and must be restored before
+ * retrieval
+ * @throws software.amazon.awssdk.core.exception.SdkClientException If any client side error occurs such as
+ * network failures or invalid presigned URL
+ * @throws S3Exception Base class for all S3 service exceptions.
+ * Unknown exceptions will be thrown as an
+ * instance of this type.
+ */
+ default CompletableFuture getObject(PresignedUrlGetObjectRequest request,
+ AsyncResponseTransformer responseTransformer) throws NoSuchKeyException,
+ InvalidObjectStateException,
+ SdkClientException,
+ S3Exception {
+ throw new UnsupportedOperationException();
+ }
+
+ // TODO: Add convenience methods :
+ // - getObject(Consumer, AsyncResponseTransformer) - consumer-based request building
+ // - getObject(PresignedUrlGetObjectRequest, Path) - direct file download
+ // - getObject(Consumer, Path) - consumer + file download
+}
diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/DefaultAsyncPresignedUrlManagerTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/DefaultAsyncPresignedUrlManagerTest.java
new file mode 100644
index 000000000000..b74ba0089189
--- /dev/null
+++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/DefaultAsyncPresignedUrlManagerTest.java
@@ -0,0 +1,373 @@
+/*
+ * 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.services.s3.internal.presignedurl;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import java.io.ByteArrayInputStream;
+import java.net.URI;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executors;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.ArgumentCaptor;
+import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider;
+import software.amazon.awssdk.awscore.client.config.AwsAdvancedClientOption;
+import software.amazon.awssdk.awscore.client.config.AwsClientOption;
+import software.amazon.awssdk.awscore.client.handler.AwsAsyncClientHandler;
+import software.amazon.awssdk.awscore.internal.AwsProtocolMetadata;
+import software.amazon.awssdk.awscore.internal.AwsServiceProtocol;
+import software.amazon.awssdk.awscore.retry.AwsRetryStrategy;
+import software.amazon.awssdk.core.ResponseBytes;
+import software.amazon.awssdk.core.async.AsyncResponseTransformer;
+import software.amazon.awssdk.core.client.config.SdkAdvancedAsyncClientOption;
+import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption;
+import software.amazon.awssdk.core.client.config.SdkClientConfiguration;
+import software.amazon.awssdk.core.client.config.SdkClientOption;
+import software.amazon.awssdk.core.client.handler.AsyncClientHandler;
+import software.amazon.awssdk.core.exception.SdkClientException;
+import software.amazon.awssdk.core.metrics.CoreMetric;
+import software.amazon.awssdk.http.AbortableInputStream;
+import software.amazon.awssdk.http.HttpExecuteResponse;
+import software.amazon.awssdk.http.SdkHttpFullResponse;
+import software.amazon.awssdk.http.SdkHttpMethod;
+import software.amazon.awssdk.http.SdkHttpRequest;
+import software.amazon.awssdk.metrics.MetricCollection;
+import software.amazon.awssdk.metrics.MetricPublisher;
+import software.amazon.awssdk.protocols.core.ExceptionMetadata;
+import software.amazon.awssdk.protocols.xml.AwsS3ProtocolFactory;
+import software.amazon.awssdk.regions.Region;
+import software.amazon.awssdk.services.s3.model.GetObjectResponse;
+import software.amazon.awssdk.services.s3.model.InvalidObjectStateException;
+import software.amazon.awssdk.services.s3.model.NoSuchKeyException;
+import software.amazon.awssdk.services.s3.model.S3Exception;
+import software.amazon.awssdk.services.s3.presignedurl.model.PresignedUrlGetObjectRequest;
+import software.amazon.awssdk.testutils.service.http.MockAsyncHttpClient;
+
+/**
+ * Tests for {@link DefaultAsyncPresignedUrlManager} using MockAsyncHttpClient to verify HTTP interactions.
+ */
+class DefaultAsyncPresignedUrlManagerTest {
+
+ private static final String TEST_CONTENT = "test-content";
+ private static final URI DEFAULT_ENDPOINT = URI.create("https://defaultendpoint.com");
+ private static final String TEST_URL = "https://test-bucket.s3.us-east-1.amazonaws.com/test-key?" +
+ "X-Amz-Date=20250707T000000Z&" +
+ "X-Amz-Signature=test-signature-value&" +
+ "X-Amz-Algorithm=AWS4-HMAC-SHA256&" +
+ "X-Amz-SignedHeaders=host&" +
+ "X-Amz-Security-Token=test-session-token&" +
+ "X-Amz-Credential=AKIAIOSFODNN7EXAMPLE%2F20250707%2Fus-east-1%2Fs3%2Faws4_request&" +
+ "X-Amz-Expires=86400";
+
+ private MockAsyncHttpClient mockHttpClient;
+ private DefaultAsyncPresignedUrlManager presignedUrlManager;
+ private PresignedUrlGetObjectRequest testRequest;
+ private AwsProtocolMetadata protocolMetadata;
+ private AwsS3ProtocolFactory protocolFactory;
+ private AsyncClientHandler clientHandler;
+
+ @BeforeEach
+ void setUp() throws Exception {
+ mockHttpClient = new MockAsyncHttpClient();
+ URL testPresignedUrl = new URL(TEST_URL);
+ testRequest = PresignedUrlGetObjectRequest.builder()
+ .presignedUrl(testPresignedUrl)
+ .build();
+
+ SdkClientConfiguration clientConfiguration = getDefaultSdkConfigs();
+ protocolMetadata = AwsProtocolMetadata.builder()
+ .serviceProtocol(AwsServiceProtocol.REST_XML)
+ .build();
+ protocolFactory = initProtocolFactory(clientConfiguration);
+ clientHandler = new AwsAsyncClientHandler(clientConfiguration);
+
+ presignedUrlManager = new DefaultAsyncPresignedUrlManager(
+ clientHandler, protocolFactory, clientConfiguration, protocolMetadata);
+ }
+
+ private static Stream httpResponseTestCases() {
+ return Stream.of(
+ Arguments.of(
+ "Success response",
+ createSuccessResponse(),
+ true,
+ null
+ ),
+ Arguments.of(
+ "404 Not Found response",
+ createErrorResponse(404, "NoSuchKey", "The specified key does not exist."),
+ false,
+ NoSuchKeyException.class
+ ),
+ Arguments.of(
+ "403 Invalid Object State response",
+ createErrorResponse(403, "InvalidObjectState", "The operation is not valid for the object's storage class."),
+ false,
+ InvalidObjectStateException.class
+ ),
+ Arguments.of(
+ "Generic error response",
+ createErrorResponse(500, "InternalError", "We encountered an internal error. Please try again."),
+ false,
+ S3Exception.class
+ )
+ );
+ }
+
+ private static Stream requestConfigurationTestCases() {
+ return Stream.of(
+ Arguments.of(
+ "Basic request",
+ (Consumer) builder ->
+ builder.presignedUrl(createTestUrl()),
+ null
+ ),
+ Arguments.of(
+ "Request with range header",
+ (Consumer) builder ->
+ builder.presignedUrl(createTestUrl()).range("bytes=0-1024"),
+ "bytes=0-1024"
+ )
+ );
+ }
+
+ private static Stream additionalTestCases() {
+ return Stream.of(
+ Arguments.of("Custom transformer test", "CUSTOM_TRANSFORMER"),
+ Arguments.of("Metrics collection test", "METRICS_COLLECTION")
+ );
+ }
+
+ private static Stream invalidUrlTestCases() {
+ return Stream.of(
+ Arguments.of("Invalid URL format", "not-a-url"),
+ Arguments.of("Empty HTTP URL", "http://"),
+ Arguments.of("Empty HTTPS URL", "https://"),
+ Arguments.of("Empty string", "")
+ );
+ }
+
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("httpResponseTestCases")
+ void given_AsyncPresignedUrlManager_when_GetObjectWithDifferentHttpResponses_then_ShouldHandleSuccessAndErrorsCorrectly(
+ String testName,
+ HttpExecuteResponse response,
+ boolean expectSuccess,
+ Class extends Exception> expectedExceptionType) {
+
+ mockHttpClient.stubNextResponse(response);
+
+ if (expectSuccess) {
+ assertSuccessfulGetObject(testRequest);
+ } else {
+ CompletableFuture> future =
+ presignedUrlManager.getObject(testRequest, AsyncResponseTransformer.toBytes());
+
+ assertThatThrownBy(future::join)
+ .hasCauseInstanceOf(expectedExceptionType);
+ }
+ }
+
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("requestConfigurationTestCases")
+ void given_AsyncPresignedUrlManager_when_GetObjectWithDifferentRequestConfigurations_then_ShouldSetCorrectHeaders(
+ String testName,
+ Consumer requestCustomizer,
+ String expectedRangeHeader) throws ExecutionException, InterruptedException {
+
+ mockHttpClient.stubNextResponse(createSuccessResponse());
+
+ PresignedUrlGetObjectRequest.Builder builder = PresignedUrlGetObjectRequest.builder();
+ requestCustomizer.accept(builder);
+ PresignedUrlGetObjectRequest request = builder.build();
+
+ CompletableFuture> future =
+ presignedUrlManager.getObject(request, AsyncResponseTransformer.toBytes());
+ ResponseBytes result = future.get();
+
+ assertThat(result).isNotNull();
+ assertThat(result.asUtf8String()).isEqualTo(TEST_CONTENT);
+
+ SdkHttpRequest lastRequest = mockHttpClient.getLastRequest();
+ assertThat(lastRequest.method()).isEqualTo(SdkHttpMethod.GET);
+
+ if (expectedRangeHeader != null) {
+ assertThat(lastRequest.firstMatchingHeader("Range"))
+ .isPresent()
+ .contains(expectedRangeHeader);
+ }
+ }
+
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("additionalTestCases")
+ void given_AsyncPresignedUrlManager_when_ExecutingDifferentScenarios_then_ShouldBehaveCorrectly(
+ String testName, String testType) throws Exception {
+
+ switch (testType) {
+ case "CUSTOM_TRANSFORMER":
+ mockHttpClient.stubNextResponse(createSuccessResponse());
+ CompletableFuture> future =
+ presignedUrlManager.getObject(testRequest, AsyncResponseTransformer.toBytes());
+ ResponseBytes result = future.get();
+ assertThat(result.asUtf8String()).isEqualTo(TEST_CONTENT);
+ break;
+
+ case "METRICS_COLLECTION":
+ MetricPublisher mockPublisher = mock(MetricPublisher.class);
+ SdkClientConfiguration clientConfigWithMetrics = getDefaultSdkConfigs().toBuilder()
+ .option(SdkClientOption.METRIC_PUBLISHERS, Collections.singletonList(mockPublisher))
+ .build();
+ DefaultAsyncPresignedUrlManager managerWithMetrics = new DefaultAsyncPresignedUrlManager(
+ clientHandler, protocolFactory, clientConfigWithMetrics, protocolMetadata);
+ mockHttpClient.stubNextResponse(createSuccessResponse());
+ CompletableFuture> metricsFuture =
+ managerWithMetrics.getObject(testRequest, AsyncResponseTransformer.toBytes());
+ metricsFuture.get();
+
+ verify(mockPublisher, atLeastOnce()).publish(any(MetricCollection.class));
+ ArgumentCaptor metricsCaptor = ArgumentCaptor.forClass(MetricCollection.class);
+ verify(mockPublisher).publish(metricsCaptor.capture());
+ MetricCollection capturedMetrics = metricsCaptor.getValue();
+ assertThat(capturedMetrics.metricValues(CoreMetric.SERVICE_ID)).contains("S3");
+ assertThat(capturedMetrics.metricValues(CoreMetric.OPERATION_NAME)).contains("GetObject");
+ break;
+ }
+ }
+
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("invalidUrlTestCases")
+ void given_AsyncPresignedUrlManager_when_GetObjectWithInvalidUrl_then_ShouldThrowException(
+ String testName, String invalidUrlString) {
+ assertThatThrownBy(() -> {
+ URL invalidUrl = new URL(invalidUrlString);
+ PresignedUrlGetObjectRequest invalidRequest = PresignedUrlGetObjectRequest.builder()
+ .presignedUrl(invalidUrl)
+ .build();
+ presignedUrlManager.getObject(invalidRequest, AsyncResponseTransformer.toBytes()).join();
+ }).satisfiesAnyOf(
+ ex -> assertThat(ex).isInstanceOf(java.net.MalformedURLException.class),
+ ex -> assertThat(ex).isInstanceOf(SdkClientException.class),
+ ex -> assertThat(ex).isInstanceOf(java.util.concurrent.CompletionException.class)
+ .extracting(Throwable::getCause)
+ .isInstanceOf(SdkClientException.class)
+ );
+ }
+
+ private SdkClientConfiguration getDefaultSdkConfigs() {
+ return SdkClientConfiguration.builder()
+ .option(SdkClientOption.ASYNC_HTTP_CLIENT, mockHttpClient)
+ .option(SdkClientOption.ADDITIONAL_HTTP_HEADERS, Collections.emptyMap())
+ .option(SdkClientOption.EXECUTION_INTERCEPTORS, Collections.emptyList())
+ .option(SdkClientOption.RETRY_STRATEGY, AwsRetryStrategy.doNotRetry())
+ .option(SdkAdvancedClientOption.USER_AGENT_PREFIX, "")
+ .option(SdkAdvancedClientOption.USER_AGENT_SUFFIX, "")
+ .option(SdkClientOption.CRC32_FROM_COMPRESSED_DATA_ENABLED, false)
+ .option(AwsClientOption.CREDENTIALS_IDENTITY_PROVIDER, AnonymousCredentialsProvider.create())
+ .option(AwsClientOption.AWS_REGION, Region.US_EAST_2)
+ .option(AwsClientOption.SIGNING_REGION, Region.US_EAST_2)
+ .option(AwsClientOption.SERVICE_SIGNING_NAME, Region.AP_EAST_2.toString())
+ .option(AwsAdvancedClientOption.ENABLE_DEFAULT_REGION_DETECTION, false)
+ .option(SdkClientOption.SCHEDULED_EXECUTOR_SERVICE, Executors.newScheduledThreadPool(1))
+ .option(SdkAdvancedAsyncClientOption.FUTURE_COMPLETION_EXECUTOR, Runnable::run)
+ .option(SdkClientOption.CLIENT_ENDPOINT_PROVIDER,
+ software.amazon.awssdk.core.ClientEndpointProvider.forEndpointOverride(DEFAULT_ENDPOINT))
+ .build();
+ }
+
+ private AwsS3ProtocolFactory initProtocolFactory(SdkClientConfiguration configuration) {
+ return AwsS3ProtocolFactory.builder()
+ .registerModeledException(
+ ExceptionMetadata.builder().errorCode("NoSuchKey")
+ .exceptionBuilderSupplier(NoSuchKeyException::builder)
+ .httpStatusCode(404).build())
+ .registerModeledException(
+ ExceptionMetadata.builder().errorCode("InvalidObjectState")
+ .exceptionBuilderSupplier(InvalidObjectStateException::builder)
+ .httpStatusCode(403).build())
+ .clientConfiguration(configuration)
+ .defaultServiceExceptionSupplier(S3Exception::builder)
+ .build();
+ }
+
+ private static URL createTestUrl() {
+ try {
+ return new URL(TEST_URL);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void assertSuccessfulGetObject(PresignedUrlGetObjectRequest request) {
+ try {
+ CompletableFuture> future =
+ presignedUrlManager.getObject(request, AsyncResponseTransformer.toBytes());
+ ResponseBytes result = future.get();
+
+ assertThat(result).isNotNull();
+ assertThat(result.asUtf8String()).isEqualTo(TEST_CONTENT);
+
+ SdkHttpRequest lastRequest = mockHttpClient.getLastRequest();
+ assertThat(lastRequest.method()).isEqualTo(SdkHttpMethod.GET);
+ assertThat(lastRequest.getUri().toString()).contains("test-bucket.s3.us-east-1.amazonaws.com/test-key");
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static HttpExecuteResponse createSuccessResponse() {
+ SdkHttpFullResponse httpResponse = SdkHttpFullResponse.builder()
+ .statusCode(200)
+ .putHeader("Content-Length", "12")
+ .putHeader("ETag", "\"test-etag\"")
+ .putHeader("Content-Type", "text/plain")
+ .build();
+ return HttpExecuteResponse.builder()
+ .response(httpResponse)
+ .responseBody(AbortableInputStream.create(
+ new ByteArrayInputStream(TEST_CONTENT.getBytes(StandardCharsets.UTF_8))))
+ .build();
+ }
+
+ private static HttpExecuteResponse createErrorResponse(int statusCode, String errorCode, String errorMessage) {
+ String errorContent = String.format(
+ "%s%s ",
+ errorCode, errorMessage);
+ SdkHttpFullResponse httpResponse = SdkHttpFullResponse.builder()
+ .statusCode(statusCode)
+ .putHeader("x-amz-request-id", "test-request-id")
+ .putHeader("x-amz-id-2", "test-extended-request-id")
+ .build();
+ return HttpExecuteResponse.builder()
+ .response(httpResponse)
+ .responseBody(AbortableInputStream.create(
+ new ByteArrayInputStream(errorContent.getBytes(StandardCharsets.UTF_8))))
+ .build();
+ }
+}
\ No newline at end of file
From 9baca167b389278e06a1ef5c824073006991aec2 Mon Sep 17 00:00:00 2001
From: jencymaryjoseph <35571282+jencymaryjoseph@users.noreply.github.com>
Date: Thu, 17 Jul 2025 09:39:05 -0700
Subject: [PATCH 05/30] Removed presignedurl api and implementation for sync
client (#6270)
---
.../DefaultPresignedUrlManager.java | 122 ------
.../s3/presignedurl/PresignedUrlManager.java | 84 ----
.../DefaultPresignedUrlManagerTest.java | 378 ------------------
3 files changed, 584 deletions(-)
delete mode 100644 services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/presignedurl/DefaultPresignedUrlManager.java
delete mode 100644 services/s3/src/main/java/software/amazon/awssdk/services/s3/presignedurl/PresignedUrlManager.java
delete mode 100644 services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/DefaultPresignedUrlManagerTest.java
diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/presignedurl/DefaultPresignedUrlManager.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/presignedurl/DefaultPresignedUrlManager.java
deleted file mode 100644
index 3527b4d830d4..000000000000
--- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/presignedurl/DefaultPresignedUrlManager.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * 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.services.s3.internal.presignedurl;
-
-import static software.amazon.awssdk.core.client.config.SdkClientOption.SIGNER_OVERRIDDEN;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.Optional;
-import software.amazon.awssdk.annotations.SdkInternalApi;
-import software.amazon.awssdk.awscore.exception.AwsServiceException;
-import software.amazon.awssdk.awscore.internal.AwsProtocolMetadata;
-import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption;
-import software.amazon.awssdk.core.client.config.SdkClientConfiguration;
-import software.amazon.awssdk.core.client.config.SdkClientOption;
-import software.amazon.awssdk.core.client.handler.ClientExecutionParams;
-import software.amazon.awssdk.core.client.handler.SyncClientHandler;
-import software.amazon.awssdk.core.exception.SdkClientException;
-import software.amazon.awssdk.core.http.HttpResponseHandler;
-import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute;
-import software.amazon.awssdk.core.metrics.CoreMetric;
-import software.amazon.awssdk.core.signer.NoOpSigner;
-import software.amazon.awssdk.core.sync.ResponseTransformer;
-import software.amazon.awssdk.metrics.MetricCollector;
-import software.amazon.awssdk.metrics.MetricPublisher;
-import software.amazon.awssdk.metrics.NoOpMetricCollector;
-import software.amazon.awssdk.protocols.xml.AwsS3ProtocolFactory;
-import software.amazon.awssdk.protocols.xml.XmlOperationMetadata;
-import software.amazon.awssdk.services.s3.internal.presignedurl.model.PresignedUrlGetObjectRequestWrapper;
-import software.amazon.awssdk.services.s3.model.GetObjectResponse;
-import software.amazon.awssdk.services.s3.model.InvalidObjectStateException;
-import software.amazon.awssdk.services.s3.model.NoSuchKeyException;
-import software.amazon.awssdk.services.s3.model.S3Exception;
-import software.amazon.awssdk.services.s3.presignedurl.PresignedUrlManager;
-import software.amazon.awssdk.services.s3.presignedurl.model.PresignedUrlGetObjectRequest;
-
-/**
- * Default implementation of {@link PresignedUrlManager} for executing S3 operations using presigned URLs.
- */
-@SdkInternalApi
-public final class DefaultPresignedUrlManager implements PresignedUrlManager {
-
- private final SyncClientHandler clientHandler;
- private final AwsS3ProtocolFactory protocolFactory;
- private final SdkClientConfiguration clientConfiguration;
- private final AwsProtocolMetadata protocolMetadata;
-
- public DefaultPresignedUrlManager(SyncClientHandler clientHandler,
- AwsS3ProtocolFactory protocolFactory,
- SdkClientConfiguration clientConfiguration,
- AwsProtocolMetadata protocolMetadata) {
- this.clientHandler = clientHandler;
- this.protocolFactory = protocolFactory;
- this.clientConfiguration = clientConfiguration;
- this.protocolMetadata = protocolMetadata;
- }
-
- @Override
- public ReturnT getObject(PresignedUrlGetObjectRequest presignedUrlGetObjectRequest,
- ResponseTransformer responseTransformer)
- throws NoSuchKeyException, InvalidObjectStateException,
- AwsServiceException, SdkClientException, S3Exception {
-
- HttpResponseHandler responseHandler = protocolFactory.createResponseHandler(
- GetObjectResponse::builder, new XmlOperationMetadata().withHasStreamingSuccessResponse(true));
-
- HttpResponseHandler errorResponseHandler = protocolFactory.createErrorResponseHandler();
-
- PresignedUrlGetObjectRequestWrapper internalRequest = PresignedUrlGetObjectRequestWrapper.builder()
- .url(presignedUrlGetObjectRequest.presignedUrl())
- .range(presignedUrlGetObjectRequest.range())
- .build();
-
- SdkClientConfiguration updatedClientConfiguration = updateSdkClientConfiguration(this.clientConfiguration);
- List metricPublishers = Optional.ofNullable(
- updatedClientConfiguration.option(SdkClientOption.METRIC_PUBLISHERS))
- .orElse(Collections.emptyList());
- MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ?
- NoOpMetricCollector.create() : MetricCollector.create("ApiCall");
- try {
- apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "S3");
- //TODO: Discuss if we need to change OPERATION_NAME as part of Surface API Review
- apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "GetObject");
-
- return clientHandler.execute(
- new ClientExecutionParams()
- .withOperationName("PresignedUrlGetObject")
- .withProtocolMetadata(protocolMetadata)
- .withResponseHandler(responseHandler)
- .withErrorResponseHandler(errorResponseHandler)
- .withRequestConfiguration(updatedClientConfiguration)
- .withInput(internalRequest)
- .withMetricCollector(apiCallMetricCollector)
- // TODO: Deprecate IS_DISCOVERED_ENDPOINT, use new SKIP_ENDPOINT_RESOLUTION for better semantics
- .putExecutionAttribute(SdkInternalExecutionAttribute.IS_DISCOVERED_ENDPOINT, true)
- .withMarshaller(new PresignedUrlGetObjectRequestMarshaller(protocolFactory)), responseTransformer);
- } finally {
- metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect()));
- }
- }
-
- private SdkClientConfiguration updateSdkClientConfiguration(SdkClientConfiguration clientConfiguration) {
- SdkClientConfiguration.Builder configBuilder = clientConfiguration.toBuilder();
- configBuilder.option(SdkAdvancedClientOption.SIGNER, new NoOpSigner());
- configBuilder.option(SIGNER_OVERRIDDEN, true);
- return configBuilder.build();
- }
-
-}
diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/presignedurl/PresignedUrlManager.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/presignedurl/PresignedUrlManager.java
deleted file mode 100644
index 3b005ce871da..000000000000
--- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/presignedurl/PresignedUrlManager.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * 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.services.s3.presignedurl;
-
-import software.amazon.awssdk.annotations.SdkPublicApi;
-import software.amazon.awssdk.core.exception.SdkClientException;
-import software.amazon.awssdk.core.sync.ResponseTransformer;
-import software.amazon.awssdk.services.s3.S3Client;
-import software.amazon.awssdk.services.s3.model.GetObjectResponse;
-import software.amazon.awssdk.services.s3.model.InvalidObjectStateException;
-import software.amazon.awssdk.services.s3.model.NoSuchKeyException;
-import software.amazon.awssdk.services.s3.model.S3Exception;
-import software.amazon.awssdk.services.s3.presignedurl.model.PresignedUrlGetObjectRequest;
-
-/**
- * Interface for executing S3 operations using presigned URLs. This can be accessed using {@link S3Client#presignedUrlManager()}.
- */
-@SdkPublicApi
-public interface PresignedUrlManager {
- /**
- *
- * Downloads an S3 object using a presigned URL.
- *
- *
- * This operation uses a presigned URL that contains all necessary authentication information, eliminating the need for AWS
- * credentials at request time. The presigned URL must be valid and not expired.
- *
- *
- * Range Requests
- *
- *
- * Supports partial object downloads using HTTP Range headers. Specify the range parameter
- * in the request to download only a portion of the object (e.g., "bytes=0-1023").
- *
- *
- * Storage Classes
- *
- *
- * If the object is stored in S3 Glacier Flexible Retrieval, S3 Glacier Deep Archive,
- * or S3 Intelligent-Tiering Archive tiers, the object must be restored before retrieval.
- * Otherwise, this operation returns an InvalidObjectState error.
- *
- *
- *
- *
- * @param request The presigned URL request containing the URL and optional range parameters
- * @param responseTransformer Transforms the response to the desired return type. See
- * {@link software.amazon.awssdk.core.sync.ResponseTransformer} for pre-built implementations like
- * downloading to a file or converting to bytes.
- * @param The type of the transformed response
- * @return The transformed result of the ResponseTransformer
- * @throws software.amazon.awssdk.services.s3.model.NoSuchKeyException The specified object does not exist
- * @throws software.amazon.awssdk.services.s3.model.InvalidObjectStateException Object is archived and must be restored before
- * retrieval
- * @throws software.amazon.awssdk.core.exception.SdkClientException If any client side error occurs such as
- * network failures or invalid presigned URL
- * @throws S3Exception Base class for all S3 service exceptions.
- * Unknown exceptions will be thrown as an
- * instance of this type.
- */
- default ReturnT getObject(PresignedUrlGetObjectRequest request,
- ResponseTransformer responseTransformer) throws NoSuchKeyException,
- InvalidObjectStateException,
- SdkClientException,
- S3Exception {
- throw new UnsupportedOperationException();
- }
-
- //TODO: Add other getObject method flavors
-}
diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/DefaultPresignedUrlManagerTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/DefaultPresignedUrlManagerTest.java
deleted file mode 100644
index ff6266689927..000000000000
--- a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/DefaultPresignedUrlManagerTest.java
+++ /dev/null
@@ -1,378 +0,0 @@
-/*
- * 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.services.s3.internal.presignedurl;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.net.URI;
-import java.net.URL;
-import java.nio.charset.StandardCharsets;
-import java.util.Collections;
-import java.util.function.Consumer;
-import java.util.stream.Stream;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.Arguments;
-import org.junit.jupiter.params.provider.MethodSource;
-import org.mockito.ArgumentCaptor;
-import software.amazon.awssdk.awscore.retry.AwsRetryStrategy;
-import software.amazon.awssdk.core.exception.SdkClientException;
-import software.amazon.awssdk.metrics.MetricCollection;
-import software.amazon.awssdk.metrics.MetricPublisher;
-import software.amazon.awssdk.core.metrics.CoreMetric;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.any;
-import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider;
-import software.amazon.awssdk.awscore.client.config.AwsAdvancedClientOption;
-import software.amazon.awssdk.awscore.client.config.AwsClientOption;
-import software.amazon.awssdk.awscore.client.handler.AwsSyncClientHandler;
-import software.amazon.awssdk.awscore.internal.AwsProtocolMetadata;
-import software.amazon.awssdk.awscore.internal.AwsServiceProtocol;
-import software.amazon.awssdk.core.ResponseInputStream;
-import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption;
-import software.amazon.awssdk.core.client.config.SdkClientConfiguration;
-import software.amazon.awssdk.core.client.config.SdkClientOption;
-import software.amazon.awssdk.core.client.handler.SyncClientHandler;
-import software.amazon.awssdk.core.sync.ResponseTransformer;
-import software.amazon.awssdk.http.AbortableInputStream;
-import software.amazon.awssdk.http.HttpExecuteResponse;
-import software.amazon.awssdk.http.SdkHttpFullResponse;
-import software.amazon.awssdk.http.SdkHttpMethod;
-import software.amazon.awssdk.http.SdkHttpRequest;
-import software.amazon.awssdk.protocols.core.ExceptionMetadata;
-import software.amazon.awssdk.protocols.xml.AwsS3ProtocolFactory;
-import software.amazon.awssdk.regions.Region;
-import software.amazon.awssdk.services.s3.model.GetObjectResponse;
-import software.amazon.awssdk.services.s3.model.InvalidObjectStateException;
-import software.amazon.awssdk.services.s3.model.NoSuchKeyException;
-import software.amazon.awssdk.services.s3.model.S3Exception;
-import software.amazon.awssdk.services.s3.presignedurl.model.PresignedUrlGetObjectRequest;
-import software.amazon.awssdk.testutils.service.http.MockSyncHttpClient;
-import software.amazon.awssdk.utils.IoUtils;
-
-/**
- * Tests for {@link DefaultPresignedUrlManager} using MockSyncHttpClient to verify HTTP interactions.
- */
- class DefaultPresignedUrlManagerTest {
-
- private static final String TEST_CONTENT = "test-content";
- private static final URI DEFAULT_ENDPOINT = URI.create("https://defaultendpoint.com");
- private static final String TEST_URL = "https://test-bucket.s3.us-east-1.amazonaws.com/test-key?" +
- "X-Amz-Date=20250707T000000Z&" +
- "X-Amz-Signature=test-signature-value&" +
- "X-Amz-Algorithm=AWS4-HMAC-SHA256&" +
- "X-Amz-SignedHeaders=host&" +
- "X-Amz-Security-Token=test-session-token&" +
- "X-Amz-Credential=AKIAIOSFODNN7EXAMPLE%2F20250707%2Fus-east-1%2Fs3%2Faws4_request&" +
- "X-Amz-Expires=86400";
- private MockSyncHttpClient mockHttpClient;
- private DefaultPresignedUrlManager presignedUrlManager;
- private URL testPresignedUrl;
- private PresignedUrlGetObjectRequest testRequest;
- private AwsProtocolMetadata protocolMetadata;
- private AwsS3ProtocolFactory protocolFactory;
- private SyncClientHandler clientHandler;
- private SdkClientConfiguration clientConfiguration;
-
- @BeforeEach
- void setUp() throws Exception {
- mockHttpClient = new MockSyncHttpClient();
- testPresignedUrl = new URL(TEST_URL);
- testRequest = PresignedUrlGetObjectRequest.builder()
- .presignedUrl(testPresignedUrl)
- .build();
-
- clientConfiguration = getDefaultSdkConfigs();
- protocolMetadata = AwsProtocolMetadata.builder()
- .serviceProtocol(AwsServiceProtocol.REST_XML)
- .build();
- protocolFactory = initProtocolFactory(clientConfiguration);
- clientHandler = new AwsSyncClientHandler(clientConfiguration);
-
- presignedUrlManager = new DefaultPresignedUrlManager(
- clientHandler, protocolFactory, clientConfiguration, protocolMetadata);
- }
-
- private static Stream httpResponseTestCases() {
- return Stream.of(
- Arguments.of(
- "Success response",
- createSuccessResponse(),
- true,
- null
- ),
- Arguments.of(
- "404 Not Found response",
- createErrorResponse(404, "NoSuchKey", "The specified key does not exist."),
- false,
- NoSuchKeyException.class
- ),
- Arguments.of(
- "403 Invalid Object State response",
- createErrorResponse(403, "InvalidObjectState", "The operation is not valid for the object's storage class."),
- false,
- InvalidObjectStateException.class
- ),
- Arguments.of(
- "Generic error response",
- createErrorResponse(500, "InternalError", "We encountered an internal error. Please try again."),
- false,
- S3Exception.class
- )
- );
- }
-
- private static Stream requestConfigurationTestCases() {
- return Stream.of(
- Arguments.of(
- "Basic request",
- (Consumer) builder ->
- builder.presignedUrl(createTestUrl()),
- null
- ),
- Arguments.of(
- "Request with range header",
- (Consumer) builder ->
- builder.presignedUrl(createTestUrl()).range("bytes=0-1024"),
- "bytes=0-1024"
- )
- );
- }
-
- @ParameterizedTest(name = "{0}")
- @MethodSource("httpResponseTestCases")
- void given_PresignedUrlManager_when_GetObjectWithDifferentHttpResponses_then_ShouldHandleSuccessAndErrorsCorrectly(String testName,
- HttpExecuteResponse response,
- boolean expectSuccess,
- Class extends Exception> expectedExceptionType) {
- mockHttpClient.stubNextResponse(response);
- if (expectSuccess) {
- assertSuccessfulGetObject(testRequest);
- } else {
- assertThatThrownBy(() -> presignedUrlManager.getObject(testRequest, ResponseTransformer.toInputStream()))
- .isInstanceOf(expectedExceptionType);
- }
- }
-
- @ParameterizedTest(name = "{0}")
- @MethodSource("requestConfigurationTestCases")
- void given_PresignedUrlManager_when_GetObjectWithDifferentRequestConfigurations_then_ShouldSetCorrectHeaders(String testName,
- Consumer requestCustomizer,
- String expectedRangeHeader) throws IOException {
- mockHttpClient.stubNextResponse(createSuccessResponse());
- PresignedUrlGetObjectRequest.Builder builder = PresignedUrlGetObjectRequest.builder();
- requestCustomizer.accept(builder);
- PresignedUrlGetObjectRequest request = builder.build();
- ResponseInputStream result = presignedUrlManager.getObject(request, ResponseTransformer.toInputStream());
- assertThat(result).isNotNull();
- String content = IoUtils.toUtf8String(result);
- assertThat(content).isEqualTo(TEST_CONTENT);
- SdkHttpRequest lastRequest = mockHttpClient.getLastRequest();
- assertThat(lastRequest.method()).isEqualTo(SdkHttpMethod.GET);
- if (expectedRangeHeader != null) {
- assertThat(lastRequest.firstMatchingHeader("Range")).isPresent()
- .contains(expectedRangeHeader);
- }
- }
-
- private static Stream additionalTestCases() {
- return Stream.of(
- Arguments.of(
- "Custom transformer test",
- "CUSTOM_TRANSFORMER"
- ),
- Arguments.of(
- "Resolved endpoint verification",
- "ENDPOINT_VERIFICATION"
- ),
- Arguments.of(
- "Metrics collection test",
- "METRICS_COLLECTION"
- )
- );
- }
-
- private static Stream invalidUrlTestCases() {
- return Stream.of(
- Arguments.of(
- "URL with spaces in path",
- "https://test-bucket.s3.us-east-1.amazonaws.com/test key with spaces"
- ),
- Arguments.of(
- "URL with invalid characters",
- "https://test-bucket.s3.us-east-1.amazonaws.com/test<>key"
- ),
- Arguments.of(
- "Malformed URL",
- "not-a-valid-url"
- )
- );
- }
-
- @ParameterizedTest(name = "{0}")
- @MethodSource("additionalTestCases")
- void given_PresignedUrlManager_when_ExecutingDifferentScenarios_then_ShouldBehaveCorrectly(String testName,
- String testType) {
- switch (testType) {
- case "CUSTOM_TRANSFORMER":
- mockHttpClient.stubNextResponse(createSuccessResponse());
- ResponseTransformer transformer =
- (response, inputStream) -> IoUtils.toUtf8String(inputStream);
- String result = presignedUrlManager.getObject(testRequest, transformer);
- assertThat(result).isEqualTo(TEST_CONTENT);
- break;
-
- case "ENDPOINT_VERIFICATION":
- mockHttpClient.stubNextResponse(createSuccessResponse());
- presignedUrlManager.getObject(testRequest, ResponseTransformer.toInputStream());
- SdkHttpRequest lastRequest = mockHttpClient.getLastRequest();
- assertThat(lastRequest.getUri().toString()).startsWith(testPresignedUrl.toString().split("\\?")[0]);
- String presignedUrlQuery = testPresignedUrl.getQuery();
- for (String param : presignedUrlQuery.split("&")) {
- String[] keyValue = param.split("=", 2);
- if (keyValue.length == 2) {
- String key = keyValue[0];
- String value = keyValue[1];
- assertThat(lastRequest.getUri().toString()).contains(key + "=" + value);
- }
- }
- break;
-
- case "METRICS_COLLECTION":
- MetricPublisher mockPublisher = mock(MetricPublisher.class);
- SdkClientConfiguration clientConfigWithMetrics = getDefaultSdkConfigs().toBuilder()
- .option(SdkClientOption.METRIC_PUBLISHERS, Collections.singletonList(mockPublisher))
- .build();
- DefaultPresignedUrlManager managerWithMetrics = new DefaultPresignedUrlManager(
- clientHandler, protocolFactory, clientConfigWithMetrics, protocolMetadata);
- mockHttpClient.stubNextResponse(createSuccessResponse());
- managerWithMetrics.getObject(testRequest, ResponseTransformer.toInputStream());
- verify(mockPublisher, atLeastOnce()).publish(any(MetricCollection.class));
- ArgumentCaptor metricsCaptor = ArgumentCaptor.forClass(MetricCollection.class);
- verify(mockPublisher).publish(metricsCaptor.capture());
- MetricCollection capturedMetrics = metricsCaptor.getValue();
- assertThat(capturedMetrics.metricValues(CoreMetric.SERVICE_ID)).contains("S3");
- assertThat(capturedMetrics.metricValues(CoreMetric.OPERATION_NAME)).contains("GetObject");
- break;
- }
- }
-
- @ParameterizedTest(name = "{0}")
- @MethodSource("invalidUrlTestCases")
- void given_PresignedUrlManager_when_GetObjectWithInvalidUrl_then_ShouldThrowException(String testName, String invalidUrlString) {
- assertThatThrownBy(() -> {
- URL invalidUrl = new URL(invalidUrlString);
- PresignedUrlGetObjectRequest invalidRequest = PresignedUrlGetObjectRequest.builder()
- .presignedUrl(invalidUrl)
- .build();
- presignedUrlManager.getObject(invalidRequest, ResponseTransformer.toInputStream());
- }).satisfiesAnyOf(
- ex -> assertThat(ex).isInstanceOf(java.net.MalformedURLException.class),
- ex -> assertThat(ex).isInstanceOf(SdkClientException.class)
- );
- }
-
- private SdkClientConfiguration getDefaultSdkConfigs() {
- return SdkClientConfiguration.builder()
- .option(SdkClientOption.SYNC_HTTP_CLIENT, mockHttpClient)
- .option(SdkClientOption.ADDITIONAL_HTTP_HEADERS, Collections.emptyMap())
- .option(SdkClientOption.EXECUTION_INTERCEPTORS, Collections.emptyList())
- .option(SdkClientOption.RETRY_STRATEGY, AwsRetryStrategy.doNotRetry())
- .option(SdkAdvancedClientOption.USER_AGENT_PREFIX, "")
- .option(SdkAdvancedClientOption.USER_AGENT_SUFFIX, "")
- .option(SdkClientOption.CRC32_FROM_COMPRESSED_DATA_ENABLED, false)
- .option(AwsClientOption.CREDENTIALS_IDENTITY_PROVIDER, AnonymousCredentialsProvider.create())
- .option(AwsClientOption.AWS_REGION, Region.US_EAST_2)
- .option(AwsClientOption.SIGNING_REGION, Region.US_EAST_2)
- .option(AwsClientOption.SERVICE_SIGNING_NAME, Region.AP_EAST_2.toString())
- .option(AwsAdvancedClientOption.ENABLE_DEFAULT_REGION_DETECTION, false)
- .option(SdkClientOption.CLIENT_ENDPOINT_PROVIDER,
- software.amazon.awssdk.core.ClientEndpointProvider.forEndpointOverride(DEFAULT_ENDPOINT))
- .build();
- }
-
- private AwsS3ProtocolFactory initProtocolFactory(SdkClientConfiguration configuration) {
- return AwsS3ProtocolFactory.builder()
- .registerModeledException(
- ExceptionMetadata.builder().errorCode("NoSuchKey")
- .exceptionBuilderSupplier(NoSuchKeyException::builder)
- .httpStatusCode(404).build())
- .registerModeledException(
- ExceptionMetadata.builder().errorCode("InvalidObjectState")
- .exceptionBuilderSupplier(InvalidObjectStateException::builder)
- .httpStatusCode(403).build())
- .clientConfiguration(configuration)
- .defaultServiceExceptionSupplier(S3Exception::builder)
- .build();
- }
-
- private static URL createTestUrl() {
- try {
- return new URL(TEST_URL);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
- private void assertSuccessfulGetObject(PresignedUrlGetObjectRequest request) {
- try {
- ResponseInputStream result = presignedUrlManager.getObject(request, ResponseTransformer.toInputStream());
- assertThat(result).isNotNull();
- String content = IoUtils.toUtf8String(result);
- assertThat(content).isEqualTo(TEST_CONTENT);
-
- SdkHttpRequest lastRequest = mockHttpClient.getLastRequest();
- assertThat(lastRequest.method()).isEqualTo(SdkHttpMethod.GET);
- assertThat(lastRequest.getUri().toString()).contains("test-bucket.s3.us-east-1.amazonaws.com/test-key");
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
- private static HttpExecuteResponse createSuccessResponse() {
- SdkHttpFullResponse httpResponse = SdkHttpFullResponse.builder()
- .statusCode(200)
- .putHeader("Content-Length", "12")
- .putHeader("ETag", "\"test-etag\"")
- .putHeader("Content-Type", "text/plain")
- .build();
- return HttpExecuteResponse.builder()
- .response(httpResponse)
- .responseBody(AbortableInputStream.create(
- new ByteArrayInputStream(TEST_CONTENT.getBytes(StandardCharsets.UTF_8))))
- .build();
- }
-
- private static HttpExecuteResponse createErrorResponse(int statusCode, String errorCode, String errorMessage) {
- String errorContent = String.format(
- "%s%s ",
- errorCode, errorMessage);
- SdkHttpFullResponse httpResponse = SdkHttpFullResponse.builder()
- .statusCode(statusCode)
- .putHeader("x-amz-request-id", "test-request-id")
- .putHeader("x-amz-id-2", "test-extended-request-id")
- .build();
- return HttpExecuteResponse.builder()
- .response(httpResponse)
- .responseBody(AbortableInputStream.create(
- new ByteArrayInputStream(errorContent.getBytes(StandardCharsets.UTF_8))))
- .build();
- }
-}
-
From 1994b7f00c74e04194487ab1b1fc48689fe7b1dc Mon Sep 17 00:00:00 2001
From: jencymaryjoseph <35571282+jencymaryjoseph@users.noreply.github.com>
Date: Thu, 17 Jul 2025 13:23:25 -0700
Subject: [PATCH 06/30] Add PresignedUrlManager support to code generated
S3AsyncClient (#6269)
* async s3 client codegen changes
* minimised service-2.json
---
.../amazon/awssdk/codegen/AddMetadata.java | 1 +
.../awssdk/codegen/internal/Constant.java | 2 +
.../customization/CustomizationConfig.java | 13 +
.../codegen/model/intermediate/Metadata.java | 18 ++
.../codegen/naming/DefaultNamingStrategy.java | 5 +
.../awssdk/codegen/naming/NamingStrategy.java | 5 +
.../awssdk/codegen/poet/PoetExtension.java | 4 +
.../codegen/poet/client/AsyncClientClass.java | 20 ++
.../poet/client/AsyncClientInterface.java | 18 ++
.../client/DelegatingAsyncClientClass.java | 5 +
.../awssdk/codegen/poet/ClientTestModels.java | 12 +
.../poet/client/AsyncClientClassTest.java | 7 +
.../poet/client/AsyncClientInterfaceTest.java | 7 +
.../DelegatingAsyncClientClassTest.java | 8 +
.../c2j/presignedurl/customization.config | 3 +
.../client/c2j/presignedurl/service-2.json | 62 +++++
...tract-async-client-class-presignedurl.java | 84 +++++++
...n-async-client-interface-presignedurl.java | 123 ++++++++++
.../poet/client/test-presignedurl-async.java | 230 ++++++++++++++++++
.../codegen-resources/customization.config | 3 +-
20 files changed, 629 insertions(+), 1 deletion(-)
create mode 100644 codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/presignedurl/customization.config
create mode 100644 codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/presignedurl/service-2.json
create mode 100644 codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-abstract-async-client-class-presignedurl.java
create mode 100644 codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-json-async-client-interface-presignedurl.java
create mode 100644 codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-presignedurl-async.java
diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/AddMetadata.java b/codegen/src/main/java/software/amazon/awssdk/codegen/AddMetadata.java
index 41d1f32693f8..f5764111371c 100644
--- a/codegen/src/main/java/software/amazon/awssdk/codegen/AddMetadata.java
+++ b/codegen/src/main/java/software/amazon/awssdk/codegen/AddMetadata.java
@@ -62,6 +62,7 @@ public static Metadata constructMetadata(ServiceModel serviceModel,
.withDocumentation(serviceModel.getDocumentation())
.withServiceAbbreviation(serviceMetadata.getServiceAbbreviation())
.withBatchmanagerPackageName(namingStrategy.getBatchManagerPackageName(serviceName))
+ .withPresignedUrlPackageName(namingStrategy.getPresignedUrlPackageName(serviceName))
.withServiceFullName(serviceMetadata.getServiceFullName())
.withServiceName(serviceName)
.withSyncClient(String.format(Constant.SYNC_CLIENT_CLASS_NAME_PATTERN, serviceName))
diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/internal/Constant.java b/codegen/src/main/java/software/amazon/awssdk/codegen/internal/Constant.java
index 5384975f49fb..a0bf024211b7 100644
--- a/codegen/src/main/java/software/amazon/awssdk/codegen/internal/Constant.java
+++ b/codegen/src/main/java/software/amazon/awssdk/codegen/internal/Constant.java
@@ -78,6 +78,8 @@ public final class Constant {
public static final String PACKAGE_NAME_BATCHMANAGER_PATTERN = "%s.batchmanager";
+ public static final String PACKAGE_NAME_PRESIGNEDURL_PATTERN = "%s.presignedurl";
+
public static final String PACKAGE_NAME_CUSTOM_AUTH_PATTERN = "%s.auth";
public static final String AUTH_POLICY_ENUM_CLASS_DIR = "software/amazon/awssdk/auth/policy/actions";
diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/model/config/customization/CustomizationConfig.java b/codegen/src/main/java/software/amazon/awssdk/codegen/model/config/customization/CustomizationConfig.java
index d3cc24a8eeae..4a7477b40290 100644
--- a/codegen/src/main/java/software/amazon/awssdk/codegen/model/config/customization/CustomizationConfig.java
+++ b/codegen/src/main/java/software/amazon/awssdk/codegen/model/config/customization/CustomizationConfig.java
@@ -351,6 +351,11 @@ public class CustomizationConfig {
*/
private boolean batchManagerSupported;
+ /**
+ * A boolean flag to indicate if Presigned URL Manager is supported.
+ */
+ private boolean presignedUrlManagerSupported;
+
/**
* A boolean flag to indicate if the fast unmarshaller code path is enabled.
*/
@@ -946,6 +951,14 @@ public void setBatchManagerSupported(boolean batchManagerSupported) {
this.batchManagerSupported = batchManagerSupported;
}
+ public boolean getPresignedUrlManagerSupported() {
+ return presignedUrlManagerSupported;
+ }
+
+ public void setPresignedUrlManagerSupported(boolean presignedUrlManagerSupported) {
+ this.presignedUrlManagerSupported = presignedUrlManagerSupported;
+ }
+
public boolean getEnableFastUnmarshaller() {
return enableFastUnmarshaller;
}
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..3cd8f5df4113 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
@@ -73,6 +73,8 @@ public class Metadata {
private String batchManagerPackageName;
+ private String presignedUrlPackageName;
+
private String endpointRulesPackageName;
private String authSchemePackageName;
@@ -815,4 +817,20 @@ public String getFullBatchManagerPackageName() {
return joinPackageNames(rootPackageName, getBatchManagerPackageName());
}
+ public Metadata withPresignedUrlPackageName(String presignedUrlPackageName) {
+ setPresignedUrlPackageName(presignedUrlPackageName);
+ return this;
+ }
+
+ public String getPresignedUrlPackageName() {
+ return presignedUrlPackageName;
+ }
+
+ public void setPresignedUrlPackageName(String presignedUrlPackageName) {
+ this.presignedUrlPackageName = presignedUrlPackageName;
+ }
+
+ public String getFullPresignedUrlPackageName() {
+ return joinPackageNames(rootPackageName, getPresignedUrlPackageName());
+ }
}
diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/naming/DefaultNamingStrategy.java b/codegen/src/main/java/software/amazon/awssdk/codegen/naming/DefaultNamingStrategy.java
index b9fcf41e6e0f..dc5257d9c9aa 100644
--- a/codegen/src/main/java/software/amazon/awssdk/codegen/naming/DefaultNamingStrategy.java
+++ b/codegen/src/main/java/software/amazon/awssdk/codegen/naming/DefaultNamingStrategy.java
@@ -220,6 +220,11 @@ public String getJmesPathPackageName(String serviceName) {
public String getBatchManagerPackageName(String serviceName) {
return getCustomizedPackageName(concatServiceNameIfShareModel(serviceName), Constant.PACKAGE_NAME_BATCHMANAGER_PATTERN);
}
+
+ @Override
+ public String getPresignedUrlPackageName(String serviceName) {
+ return getCustomizedPackageName(concatServiceNameIfShareModel(serviceName), Constant.PACKAGE_NAME_PRESIGNEDURL_PATTERN);
+ }
@Override
public String getSmokeTestPackageName(String serviceName) {
diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/naming/NamingStrategy.java b/codegen/src/main/java/software/amazon/awssdk/codegen/naming/NamingStrategy.java
index 5b22363970f0..1047a3a1904b 100644
--- a/codegen/src/main/java/software/amazon/awssdk/codegen/naming/NamingStrategy.java
+++ b/codegen/src/main/java/software/amazon/awssdk/codegen/naming/NamingStrategy.java
@@ -93,6 +93,11 @@ public interface NamingStrategy {
* Retrieve the batchManager package name that should be used based on the service name.
*/
String getBatchManagerPackageName(String serviceName);
+
+ /**
+ * Retrieve the presignedUrl package name that should be used based on the service name.
+ */
+ String getPresignedUrlPackageName(String serviceName);
/**
* Retrieve the smote test package name that should be used based on the service name.
diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/PoetExtension.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/PoetExtension.java
index f119507809c2..54888ce41b57 100644
--- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/PoetExtension.java
+++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/PoetExtension.java
@@ -209,4 +209,8 @@ public ClassName getBatchManagerAsyncInterface() {
return ClassName.get(model.getMetadata().getFullBatchManagerPackageName(),
model.getMetadata().getServiceName() + "AsyncBatchManager");
}
+
+ public ClassName getPresignedUrlManagerAsyncInterface() {
+ return ClassName.get(model.getMetadata().getFullPresignedUrlPackageName(), "AsyncPresignedUrlManager");
+ }
}
diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/AsyncClientClass.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/AsyncClientClass.java
index 539d5df35a43..d29c3124475b 100644
--- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/AsyncClientClass.java
+++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/AsyncClientClass.java
@@ -538,6 +538,26 @@ protected void addBatchManagerMethod(Builder type) {
type.addMethod(batchManager);
}
+
+ @Override
+ protected void addPresignedUrlManagerMethod(Builder type) {
+ ClassName returnType = poetExtensions.getPresignedUrlManagerAsyncInterface();
+ String internalPresignedUrlPackage = model.getMetadata().getFullInternalPackageName() + ".presignedurl";
+ ClassName implClass = ClassName.get(internalPresignedUrlPackage, "DefaultAsyncPresignedUrlManager");
+
+ MethodSpec presignedUrlManager = MethodSpec.methodBuilder("presignedUrlManager")
+ .addModifiers(PUBLIC)
+ .addAnnotation(Override.class)
+ .returns(returnType)
+ .addStatement("return new $T(clientHandler,"
+ + " protocolFactory, "
+ + "clientConfiguration,"
+ + " protocolMetadata)",
+ implClass)
+ .build();
+
+ type.addMethod(presignedUrlManager);
+ }
private MethodSpec resolveMetricPublishersMethod() {
String clientConfigName = "clientConfiguration";
diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/AsyncClientInterface.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/AsyncClientInterface.java
index cbd5f3cb3e9a..ab4e9223ff40 100644
--- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/AsyncClientInterface.java
+++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/AsyncClientInterface.java
@@ -99,6 +99,9 @@ public TypeSpec poetSpec() {
if (model.getCustomizationConfig().getBatchManagerSupported()) {
addBatchManagerMethod(result);
}
+ if (model.getCustomizationConfig().getPresignedUrlManagerSupported()) {
+ addPresignedUrlManagerMethod(result);
+ }
result.addMethod(serviceClientConfigMethod());
addAdditionalMethods(result);
addCloseMethod(result);
@@ -174,6 +177,16 @@ protected void addBatchManagerMethod(TypeSpec.Builder type) {
+ "configuration set on this client.", returnType);
type.addMethod(batchManagerOperationBody(builder).build());
}
+
+ protected void addPresignedUrlManagerMethod(TypeSpec.Builder type) {
+ ClassName returnType = poetExtensions.getPresignedUrlManagerAsyncInterface();
+ MethodSpec.Builder builder = MethodSpec.methodBuilder("presignedUrlManager")
+ .addModifiers(PUBLIC)
+ .returns(returnType)
+ .addJavadoc("Creates an instance of {@link $T} object with the "
+ + "configuration set on this client.", returnType);
+ type.addMethod(presignedUrlManagerOperationBody(builder).build());
+ }
@Override
public ClassName className() {
@@ -550,5 +563,10 @@ protected MethodSpec.Builder batchManagerOperationBody(MethodSpec.Builder builde
return builder.addModifiers(DEFAULT, PUBLIC)
.addStatement("throw new $T()", UnsupportedOperationException.class);
}
+
+ protected MethodSpec.Builder presignedUrlManagerOperationBody(MethodSpec.Builder builder) {
+ return builder.addModifiers(DEFAULT, PUBLIC)
+ .addStatement("throw new $T()", UnsupportedOperationException.class);
+ }
}
diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/DelegatingAsyncClientClass.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/DelegatingAsyncClientClass.java
index 5af2c45ad0bc..be0239e00808 100644
--- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/DelegatingAsyncClientClass.java
+++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/DelegatingAsyncClientClass.java
@@ -222,4 +222,9 @@ protected MethodSpec.Builder waiterOperationBody(MethodSpec.Builder builder) {
protected MethodSpec.Builder batchManagerOperationBody(MethodSpec.Builder builder) {
return builder.addAnnotation(Override.class).addStatement("return delegate.batchManager()");
}
+
+ @Override
+ protected MethodSpec.Builder presignedUrlManagerOperationBody(MethodSpec.Builder builder) {
+ return builder.addAnnotation(Override.class).addStatement("return delegate.presignedUrlManager()");
+ }
}
diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/ClientTestModels.java b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/ClientTestModels.java
index acd08400564a..82a80e58652b 100644
--- a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/ClientTestModels.java
+++ b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/ClientTestModels.java
@@ -605,6 +605,18 @@ public static IntermediateModel batchManagerModels() {
return new IntermediateModelBuilder(models).build();
}
+
+ public static IntermediateModel presignedUrlManagerModels() {
+ File serviceModel = new File(ClientTestModels.class.getResource("client/c2j/presignedurl/service-2.json").getFile());
+ File customizationModel = new File(ClientTestModels.class.getResource("client/c2j/presignedurl/customization.config").getFile());
+
+ C2jModels models = C2jModels.builder()
+ .serviceModel(getServiceModel(serviceModel))
+ .customizationConfig(getCustomizationConfig(customizationModel))
+ .build();
+
+ return new IntermediateModelBuilder(models).build();
+ }
private static ServiceModel getServiceModel(File file) {
return ModelLoaderUtils.loadModel(ServiceModel.class, file);
diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/client/AsyncClientClassTest.java b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/client/AsyncClientClassTest.java
index 6e2082fc4175..7a7c74375cb9 100644
--- a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/client/AsyncClientClassTest.java
+++ b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/client/AsyncClientClassTest.java
@@ -24,6 +24,7 @@
import static software.amazon.awssdk.codegen.poet.ClientTestModels.customPackageModels;
import static software.amazon.awssdk.codegen.poet.ClientTestModels.endpointDiscoveryModels;
import static software.amazon.awssdk.codegen.poet.ClientTestModels.opsWithSigv4a;
+import static software.amazon.awssdk.codegen.poet.ClientTestModels.presignedUrlManagerModels;
import static software.amazon.awssdk.codegen.poet.ClientTestModels.serviceWithCustomContextParamsModels;
import static software.amazon.awssdk.codegen.poet.ClientTestModels.queryServiceModels;
import static software.amazon.awssdk.codegen.poet.ClientTestModels.restJsonServiceModels;
@@ -102,6 +103,12 @@ public void asyncClientBatchManager() {
ClassSpec aSyncClientBatchManager = createAsyncClientClass(batchManagerModels());
assertThat(aSyncClientBatchManager, generatesTo("test-batchmanager-async.java"));
}
+
+ @Test
+ public void asyncClientPresignedUrlManager() {
+ ClassSpec asyncClientPresignedUrlManager = createAsyncClientClass(presignedUrlManagerModels());
+ assertThat(asyncClientPresignedUrlManager, generatesTo("test-presignedurl-async.java"));
+ }
@Test
public void asyncClientWithStreamingUnsignedPayload() {
diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/client/AsyncClientInterfaceTest.java b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/client/AsyncClientInterfaceTest.java
index fda4c6f2b459..1a2f18183e8d 100644
--- a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/client/AsyncClientInterfaceTest.java
+++ b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/client/AsyncClientInterfaceTest.java
@@ -17,6 +17,7 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static software.amazon.awssdk.codegen.poet.ClientTestModels.batchManagerModels;
+import static software.amazon.awssdk.codegen.poet.ClientTestModels.presignedUrlManagerModels;
import static software.amazon.awssdk.codegen.poet.ClientTestModels.restJsonServiceModels;
import static software.amazon.awssdk.codegen.poet.PoetMatchers.generatesTo;
@@ -35,4 +36,10 @@ public void asyncClientInterfaceWithBatchManager() {
ClassSpec asyncClientInterface = new AsyncClientInterface(batchManagerModels());
assertThat(asyncClientInterface, generatesTo("test-json-async-client-interface-batchmanager.java"));
}
+
+ @Test
+ public void asyncClientInterfaceWithPresignedUrlManager() {
+ ClassSpec asyncClientInterface = new AsyncClientInterface(presignedUrlManagerModels());
+ assertThat(asyncClientInterface, generatesTo("test-json-async-client-interface-presignedurl.java"));
+ }
}
diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/client/DelegatingAsyncClientClassTest.java b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/client/DelegatingAsyncClientClassTest.java
index a12ab3ac659c..a77cb67ab727 100644
--- a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/client/DelegatingAsyncClientClassTest.java
+++ b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/client/DelegatingAsyncClientClassTest.java
@@ -16,6 +16,7 @@
package software.amazon.awssdk.codegen.poet.client;
import static org.hamcrest.MatcherAssert.assertThat;
+import static software.amazon.awssdk.codegen.poet.ClientTestModels.presignedUrlManagerModels;
import static software.amazon.awssdk.codegen.poet.ClientTestModels.restJsonServiceModels;
import static software.amazon.awssdk.codegen.poet.PoetMatchers.generatesTo;
@@ -28,4 +29,11 @@ public void delegatingAsyncClientClass() {
new DelegatingAsyncClientClass(restJsonServiceModels());
assertThat(asyncClientDecoratorAbstractClass, generatesTo("test-abstract-async-client-class.java"));
}
+
+ @Test
+ public void delegatingAsyncClientClassWithPresignedUrlManager() {
+ DelegatingAsyncClientClass asyncClientDecoratorAbstractClass =
+ new DelegatingAsyncClientClass(presignedUrlManagerModels());
+ assertThat(asyncClientDecoratorAbstractClass, generatesTo("test-abstract-async-client-class-presignedurl.java"));
+ }
}
diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/presignedurl/customization.config b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/presignedurl/customization.config
new file mode 100644
index 000000000000..cfc5b2874b7a
--- /dev/null
+++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/presignedurl/customization.config
@@ -0,0 +1,3 @@
+{
+ "presignedUrlManagerSupported": true
+}
\ No newline at end of file
diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/presignedurl/service-2.json b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/presignedurl/service-2.json
new file mode 100644
index 000000000000..cfeb496ac347
--- /dev/null
+++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/presignedurl/service-2.json
@@ -0,0 +1,62 @@
+{
+ "version": "2.0",
+ "metadata": {
+ "apiVersion": "2010-05-08",
+ "endpointPrefix": "json-service-endpoint",
+ "globalEndpoint": "json-service.amazonaws.com",
+ "protocol": "json",
+ "serviceAbbreviation": "Json Service",
+ "serviceFullName": "Some Service That Uses AWS Json Protocol",
+ "serviceId": "Json Service",
+ "signingName": "json-service",
+ "signatureVersion": "v4",
+ "uid": "json-service-2010-05-08",
+ "xmlNamespace": "https://json-service.amazonaws.com/doc/2010-05-08/"
+ },
+ "operations": {
+ "APostOperation": {
+ "name": "APostOperation",
+ "http": {
+ "method": "POST",
+ "requestUri": "/"
+ },
+ "input": {
+ "shape": "APostOperationRequest"
+ },
+ "errors": [
+ {
+ "shape": "InvalidInputException"
+ }
+ ],
+ "documentation": "Performs a post operation to the query service and has no output
"
+ }
+ },
+ "shapes": {
+ "APostOperationRequest": {
+ "type": "structure",
+ "required": [
+ "StringMember"
+ ],
+ "members": {
+ "StringMember": {
+ "shape": "String",
+ "documentation": "String member
"
+ }
+ }
+ },
+ "InvalidInputException": {
+ "type": "structure",
+ "members": {
+ "message": {
+ "shape": "String"
+ }
+ },
+ "documentation": "The request was rejected because an invalid or out-of-range value was supplied for an input parameter.
",
+ "exception": true
+ },
+ "String": {
+ "type": "string"
+ }
+ },
+ "documentation": "A service that is implemented using the json protocol"
+}
\ No newline at end of file
diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-abstract-async-client-class-presignedurl.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-abstract-async-client-class-presignedurl.java
new file mode 100644
index 000000000000..715c56f752a7
--- /dev/null
+++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-abstract-async-client-class-presignedurl.java
@@ -0,0 +1,84 @@
+package software.amazon.awssdk.services.json;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Function;
+import software.amazon.awssdk.annotations.Generated;
+import software.amazon.awssdk.annotations.SdkPublicApi;
+import software.amazon.awssdk.core.SdkClient;
+import software.amazon.awssdk.services.json.model.APostOperationRequest;
+import software.amazon.awssdk.services.json.model.APostOperationResponse;
+import software.amazon.awssdk.services.json.model.JsonRequest;
+import software.amazon.awssdk.services.json.presignedurl.AsyncPresignedUrlManager;
+import software.amazon.awssdk.utils.Validate;
+
+@Generated("software.amazon.awssdk:codegen")
+@SdkPublicApi
+public abstract class DelegatingJsonAsyncClient implements JsonAsyncClient {
+ private final JsonAsyncClient delegate;
+
+ public DelegatingJsonAsyncClient(JsonAsyncClient delegate) {
+ Validate.paramNotNull(delegate, "delegate");
+ this.delegate = delegate;
+ }
+
+ /**
+ *
+ * Performs a post operation to the query service and has no output
+ *
+ *
+ * @param aPostOperationRequest
+ * @return A Java Future containing the result of the APostOperation operation returned by the service.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following
+ * exceptions. The exception returned is wrapped with CompletionException, so you need to invoke
+ * {@link Throwable#getCause} to retrieve the underlying exception.
+ *
+ * InvalidInputException The request was rejected because an invalid or out-of-range value was supplied
+ * for an input parameter.
+ * SdkException Base class for all exceptions that can be thrown by the SDK (both service and client).
+ * Can be used for catch all scenarios.
+ * SdkClientException If any client side error occurs such as an IO related failure, failure to get
+ * credentials, etc.
+ * JsonException Base class for all service exceptions. Unknown exceptions will be thrown as an instance
+ * of this type.
+ *
+ * @sample JsonAsyncClient.APostOperation
+ * @see AWS
+ * API Documentation
+ */
+ @Override
+ public CompletableFuture aPostOperation(APostOperationRequest aPostOperationRequest) {
+ return invokeOperation(aPostOperationRequest, request -> delegate.aPostOperation(request));
+ }
+
+ /**
+ * Creates an instance of {@link AsyncPresignedUrlManager} object with the configuration set on this client.
+ */
+ @Override
+ public AsyncPresignedUrlManager presignedUrlManager() {
+ return delegate.presignedUrlManager();
+ }
+
+ @Override
+ public final JsonServiceClientConfiguration serviceClientConfiguration() {
+ return delegate.serviceClientConfiguration();
+ }
+
+ @Override
+ public final String serviceName() {
+ return delegate.serviceName();
+ }
+
+ public SdkClient delegate() {
+ return this.delegate;
+ }
+
+ protected CompletableFuture invokeOperation(T request,
+ Function> operation) {
+ return operation.apply(request);
+ }
+
+ @Override
+ public void close() {
+ delegate.close();
+ }
+}
diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-json-async-client-interface-presignedurl.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-json-async-client-interface-presignedurl.java
new file mode 100644
index 000000000000..631198b323bd
--- /dev/null
+++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-json-async-client-interface-presignedurl.java
@@ -0,0 +1,123 @@
+package software.amazon.awssdk.services.json;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Consumer;
+import software.amazon.awssdk.annotations.Generated;
+import software.amazon.awssdk.annotations.SdkPublicApi;
+import software.amazon.awssdk.annotations.ThreadSafe;
+import software.amazon.awssdk.awscore.AwsClient;
+import software.amazon.awssdk.services.json.model.APostOperationRequest;
+import software.amazon.awssdk.services.json.model.APostOperationResponse;
+import software.amazon.awssdk.services.json.presignedurl.AsyncPresignedUrlManager;
+
+/**
+ * Service client for accessing Json Service asynchronously. This can be created using the static {@link #builder()}
+ * method.The asynchronous client performs non-blocking I/O when configured with any {@code SdkAsyncHttpClient}
+ * supported in the SDK. However, full non-blocking is not guaranteed as the async client may perform blocking calls in
+ * some cases such as credentials retrieval and endpoint discovery as part of the async API call.
+ *
+ * A service that is implemented using the json protocol
+ */
+@Generated("software.amazon.awssdk:codegen")
+@SdkPublicApi
+@ThreadSafe
+public interface JsonAsyncClient extends AwsClient {
+ String SERVICE_NAME = "json-service";
+
+ /**
+ * Value for looking up the service's metadata from the
+ * {@link software.amazon.awssdk.regions.ServiceMetadataProvider}.
+ */
+ String SERVICE_METADATA_ID = "json-service-endpoint";
+
+ /**
+ *
+ * Performs a post operation to the query service and has no output
+ *
+ *
+ * @param aPostOperationRequest
+ * @return A Java Future containing the result of the APostOperation operation returned by the service.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following
+ * exceptions. The exception returned is wrapped with CompletionException, so you need to invoke
+ * {@link Throwable#getCause} to retrieve the underlying exception.
+ *
+ * InvalidInputException The request was rejected because an invalid or out-of-range value was supplied
+ * for an input parameter.
+ * SdkException Base class for all exceptions that can be thrown by the SDK (both service and client).
+ * Can be used for catch all scenarios.
+ * SdkClientException If any client side error occurs such as an IO related failure, failure to get
+ * credentials, etc.
+ * JsonException Base class for all service exceptions. Unknown exceptions will be thrown as an instance
+ * of this type.
+ *
+ * @sample JsonAsyncClient.APostOperation
+ * @see AWS
+ * API Documentation
+ */
+ default CompletableFuture aPostOperation(APostOperationRequest aPostOperationRequest) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ *
+ * Performs a post operation to the query service and has no output
+ *
+ *
+ *
+ * This is a convenience which creates an instance of the {@link APostOperationRequest.Builder} avoiding the need to
+ * create one manually via {@link APostOperationRequest#builder()}
+ *
+ *
+ * @param aPostOperationRequest
+ * A {@link Consumer} that will call methods on
+ * {@link software.amazon.awssdk.services.json.model.APostOperationRequest.Builder} to create a request.
+ * @return A Java Future containing the result of the APostOperation operation returned by the service.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following
+ * exceptions. The exception returned is wrapped with CompletionException, so you need to invoke
+ * {@link Throwable#getCause} to retrieve the underlying exception.
+ *
+ * InvalidInputException The request was rejected because an invalid or out-of-range value was supplied
+ * for an input parameter.
+ * SdkException Base class for all exceptions that can be thrown by the SDK (both service and client).
+ * Can be used for catch all scenarios.
+ * SdkClientException If any client side error occurs such as an IO related failure, failure to get
+ * credentials, etc.
+ * JsonException Base class for all service exceptions. Unknown exceptions will be thrown as an instance
+ * of this type.
+ *
+ * @sample JsonAsyncClient.APostOperation
+ * @see AWS
+ * API Documentation
+ */
+ default CompletableFuture aPostOperation(Consumer aPostOperationRequest) {
+ return aPostOperation(APostOperationRequest.builder().applyMutation(aPostOperationRequest).build());
+ }
+
+ /**
+ * Creates an instance of {@link AsyncPresignedUrlManager} object with the configuration set on this client.
+ */
+ default AsyncPresignedUrlManager presignedUrlManager() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ default JsonServiceClientConfiguration serviceClientConfiguration() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Create a {@link JsonAsyncClient} with the region loaded from the
+ * {@link software.amazon.awssdk.regions.providers.DefaultAwsRegionProviderChain} and credentials loaded from the
+ * {@link software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider}.
+ */
+ static JsonAsyncClient create() {
+ return builder().build();
+ }
+
+ /**
+ * Create a builder that can be used to configure and create a {@link JsonAsyncClient}.
+ */
+ static JsonAsyncClientBuilder builder() {
+ return new DefaultJsonAsyncClientBuilder();
+ }
+}
diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-presignedurl-async.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-presignedurl-async.java
new file mode 100644
index 000000000000..5afef2048e82
--- /dev/null
+++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-presignedurl-async.java
@@ -0,0 +1,230 @@
+package software.amazon.awssdk.services.json;
+
+import static software.amazon.awssdk.utils.FunctionalUtils.runAndLogError;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import software.amazon.awssdk.annotations.Generated;
+import software.amazon.awssdk.annotations.SdkInternalApi;
+import software.amazon.awssdk.awscore.client.handler.AwsAsyncClientHandler;
+import software.amazon.awssdk.awscore.exception.AwsServiceException;
+import software.amazon.awssdk.awscore.internal.AwsProtocolMetadata;
+import software.amazon.awssdk.awscore.internal.AwsServiceProtocol;
+import software.amazon.awssdk.awscore.retry.AwsRetryStrategy;
+import software.amazon.awssdk.core.RequestOverrideConfiguration;
+import software.amazon.awssdk.core.SdkPlugin;
+import software.amazon.awssdk.core.SdkRequest;
+import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration;
+import software.amazon.awssdk.core.client.config.SdkClientConfiguration;
+import software.amazon.awssdk.core.client.config.SdkClientOption;
+import software.amazon.awssdk.core.client.handler.AsyncClientHandler;
+import software.amazon.awssdk.core.client.handler.ClientExecutionParams;
+import software.amazon.awssdk.core.http.HttpResponseHandler;
+import software.amazon.awssdk.core.metrics.CoreMetric;
+import software.amazon.awssdk.core.retry.RetryMode;
+import software.amazon.awssdk.metrics.MetricCollector;
+import software.amazon.awssdk.metrics.MetricPublisher;
+import software.amazon.awssdk.metrics.NoOpMetricCollector;
+import software.amazon.awssdk.protocols.core.ExceptionMetadata;
+import software.amazon.awssdk.protocols.json.AwsJsonProtocol;
+import software.amazon.awssdk.protocols.json.AwsJsonProtocolFactory;
+import software.amazon.awssdk.protocols.json.BaseAwsJsonProtocolFactory;
+import software.amazon.awssdk.protocols.json.JsonOperationMetadata;
+import software.amazon.awssdk.retries.api.RetryStrategy;
+import software.amazon.awssdk.services.json.internal.JsonServiceClientConfigurationBuilder;
+import software.amazon.awssdk.services.json.internal.ServiceVersionInfo;
+import software.amazon.awssdk.services.json.internal.presignedurl.DefaultAsyncPresignedUrlManager;
+import software.amazon.awssdk.services.json.model.APostOperationRequest;
+import software.amazon.awssdk.services.json.model.APostOperationResponse;
+import software.amazon.awssdk.services.json.model.InvalidInputException;
+import software.amazon.awssdk.services.json.model.JsonException;
+import software.amazon.awssdk.services.json.presignedurl.AsyncPresignedUrlManager;
+import software.amazon.awssdk.services.json.transform.APostOperationRequestMarshaller;
+import software.amazon.awssdk.utils.CompletableFutureUtils;
+
+/**
+ * Internal implementation of {@link JsonAsyncClient}.
+ *
+ * @see JsonAsyncClient#builder()
+ */
+@Generated("software.amazon.awssdk:codegen")
+@SdkInternalApi
+final class DefaultJsonAsyncClient implements JsonAsyncClient {
+ private static final Logger log = LoggerFactory.getLogger(DefaultJsonAsyncClient.class);
+
+ private static final AwsProtocolMetadata protocolMetadata = AwsProtocolMetadata.builder()
+ .serviceProtocol(AwsServiceProtocol.AWS_JSON).build();
+
+ private final AsyncClientHandler clientHandler;
+
+ private final AwsJsonProtocolFactory protocolFactory;
+
+ private final SdkClientConfiguration clientConfiguration;
+
+ protected DefaultJsonAsyncClient(SdkClientConfiguration clientConfiguration) {
+ this.clientHandler = new AwsAsyncClientHandler(clientConfiguration);
+ this.clientConfiguration = clientConfiguration.toBuilder().option(SdkClientOption.SDK_CLIENT, this)
+ .option(SdkClientOption.API_METADATA, "Json_Service" + "#" + ServiceVersionInfo.VERSION).build();
+ this.protocolFactory = init(AwsJsonProtocolFactory.builder()).build();
+ }
+
+ /**
+ *
+ * Performs a post operation to the query service and has no output
+ *
+ *
+ * @param aPostOperationRequest
+ * @return A Java Future containing the result of the APostOperation operation returned by the service.
+ * The CompletableFuture returned by this method can be completed exceptionally with the following
+ * exceptions. The exception returned is wrapped with CompletionException, so you need to invoke
+ * {@link Throwable#getCause} to retrieve the underlying exception.
+ *
+ * InvalidInputException The request was rejected because an invalid or out-of-range value was supplied
+ * for an input parameter.
+ * SdkException Base class for all exceptions that can be thrown by the SDK (both service and client).
+ * Can be used for catch all scenarios.
+ * SdkClientException If any client side error occurs such as an IO related failure, failure to get
+ * credentials, etc.
+ * JsonException Base class for all service exceptions. Unknown exceptions will be thrown as an instance
+ * of this type.
+ *
+ * @sample JsonAsyncClient.APostOperation
+ * @see AWS
+ * API Documentation
+ */
+ @Override
+ public CompletableFuture aPostOperation(APostOperationRequest aPostOperationRequest) {
+ SdkClientConfiguration clientConfiguration = updateSdkClientConfiguration(aPostOperationRequest, this.clientConfiguration);
+ List metricPublishers = resolveMetricPublishers(clientConfiguration, aPostOperationRequest
+ .overrideConfiguration().orElse(null));
+ MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ? NoOpMetricCollector.create() : MetricCollector
+ .create("ApiCall");
+ try {
+ apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Json Service");
+ apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "APostOperation");
+ JsonOperationMetadata operationMetadata = JsonOperationMetadata.builder().hasStreamingSuccessResponse(false)
+ .isPayloadJson(true).build();
+
+ HttpResponseHandler responseHandler = protocolFactory.createResponseHandler(
+ operationMetadata, APostOperationResponse::builder);
+ Function> exceptionMetadataMapper = errorCode -> {
+ if (errorCode == null) {
+ return Optional.empty();
+ }
+ switch (errorCode) {
+ case "InvalidInputException":
+ return Optional.of(ExceptionMetadata.builder().errorCode("InvalidInputException").httpStatusCode(400)
+ .exceptionBuilderSupplier(InvalidInputException::builder).build());
+ default:
+ return Optional.empty();
+ }
+ };
+ HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory,
+ operationMetadata, exceptionMetadataMapper);
+
+ CompletableFuture executeFuture = clientHandler
+ .execute(new ClientExecutionParams()
+ .withOperationName("APostOperation").withProtocolMetadata(protocolMetadata)
+ .withMarshaller(new APostOperationRequestMarshaller(protocolFactory))
+ .withResponseHandler(responseHandler).withErrorResponseHandler(errorResponseHandler)
+ .withRequestConfiguration(clientConfiguration).withMetricCollector(apiCallMetricCollector)
+ .withInput(aPostOperationRequest));
+ CompletableFuture whenCompleted = executeFuture.whenComplete((r, e) -> {
+ metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect()));
+ });
+ executeFuture = CompletableFutureUtils.forwardExceptionTo(whenCompleted, executeFuture);
+ return executeFuture;
+ } catch (Throwable t) {
+ metricPublishers.forEach(p -> p.publish(apiCallMetricCollector.collect()));
+ return CompletableFutureUtils.failedFuture(t);
+ }
+ }
+
+ @Override
+ public AsyncPresignedUrlManager presignedUrlManager() {
+ return new DefaultAsyncPresignedUrlManager(clientHandler, protocolFactory, clientConfiguration, protocolMetadata);
+ }
+
+ @Override
+ public final JsonServiceClientConfiguration serviceClientConfiguration() {
+ return new JsonServiceClientConfigurationBuilder(this.clientConfiguration.toBuilder()).build();
+ }
+
+ @Override
+ public final String serviceName() {
+ return SERVICE_NAME;
+ }
+
+ private > T init(T builder) {
+ return builder.clientConfiguration(clientConfiguration).defaultServiceExceptionSupplier(JsonException::builder)
+ .protocol(AwsJsonProtocol.AWS_JSON).protocolVersion("1.1");
+ }
+
+ private static List resolveMetricPublishers(SdkClientConfiguration clientConfiguration,
+ RequestOverrideConfiguration requestOverrideConfiguration) {
+ List publishers = null;
+ if (requestOverrideConfiguration != null) {
+ publishers = requestOverrideConfiguration.metricPublishers();
+ }
+ if (publishers == null || publishers.isEmpty()) {
+ publishers = clientConfiguration.option(SdkClientOption.METRIC_PUBLISHERS);
+ }
+ if (publishers == null) {
+ publishers = Collections.emptyList();
+ }
+ return publishers;
+ }
+
+ private void updateRetryStrategyClientConfiguration(SdkClientConfiguration.Builder configuration) {
+ ClientOverrideConfiguration.Builder builder = configuration.asOverrideConfigurationBuilder();
+ RetryMode retryMode = builder.retryMode();
+ if (retryMode != null) {
+ configuration.option(SdkClientOption.RETRY_STRATEGY, AwsRetryStrategy.forRetryMode(retryMode));
+ } else {
+ Consumer> configurator = builder.retryStrategyConfigurator();
+ if (configurator != null) {
+ RetryStrategy.Builder, ?> defaultBuilder = AwsRetryStrategy.defaultRetryStrategy().toBuilder();
+ configurator.accept(defaultBuilder);
+ configuration.option(SdkClientOption.RETRY_STRATEGY, defaultBuilder.build());
+ } else {
+ RetryStrategy retryStrategy = builder.retryStrategy();
+ if (retryStrategy != null) {
+ configuration.option(SdkClientOption.RETRY_STRATEGY, retryStrategy);
+ }
+ }
+ }
+ configuration.option(SdkClientOption.CONFIGURED_RETRY_MODE, null);
+ configuration.option(SdkClientOption.CONFIGURED_RETRY_STRATEGY, null);
+ configuration.option(SdkClientOption.CONFIGURED_RETRY_CONFIGURATOR, null);
+ }
+
+ private SdkClientConfiguration updateSdkClientConfiguration(SdkRequest request, SdkClientConfiguration clientConfiguration) {
+ List plugins = request.overrideConfiguration().map(c -> c.plugins()).orElse(Collections.emptyList());
+ SdkClientConfiguration.Builder configuration = clientConfiguration.toBuilder();
+ if (plugins.isEmpty()) {
+ return configuration.build();
+ }
+ JsonServiceClientConfigurationBuilder serviceConfigBuilder = new JsonServiceClientConfigurationBuilder(configuration);
+ for (SdkPlugin plugin : plugins) {
+ plugin.configureClient(serviceConfigBuilder);
+ }
+ updateRetryStrategyClientConfiguration(configuration);
+ return configuration.build();
+ }
+
+ private HttpResponseHandler createErrorResponseHandler(BaseAwsJsonProtocolFactory protocolFactory,
+ JsonOperationMetadata operationMetadata, Function> exceptionMetadataMapper) {
+ return protocolFactory.createErrorResponseHandler(operationMetadata, exceptionMetadataMapper);
+ }
+
+ @Override
+ public void close() {
+ clientHandler.close();
+ }
+}
diff --git a/services/s3/src/main/resources/codegen-resources/customization.config b/services/s3/src/main/resources/codegen-resources/customization.config
index 4dee2d9e88d9..0d9b8038bd1f 100644
--- a/services/s3/src/main/resources/codegen-resources/customization.config
+++ b/services/s3/src/main/resources/codegen-resources/customization.config
@@ -358,5 +358,6 @@
"methodName": "modifyUploadPartCopyRequest",
"className": "software.amazon.awssdk.services.s3.internal.CustomRequestTransformerUtils"
}
- }
+ },
+ "presignedUrlManagerSupported": true
}
From 2b23126cf099770c6d9a3c1a5803f64a1d575538 Mon Sep 17 00:00:00 2001
From: jencymaryjoseph <35571282+jencymaryjoseph@users.noreply.github.com>
Date: Fri, 18 Jul 2025 15:19:25 -0700
Subject: [PATCH 07/30] Modify Operation Name for Presigned URL, add Null
Check for ResolvedEndpoint, enhance AsyncPresignedUrlManager API (#6275)
* modify operation name, null check for resolvedendpoint, add getObject APIs
* added null check for resolvedendpointHeader, doc updates, test additions
* fixed checkstyle issue
---
.../EndpointResolverInterceptorSpec.java | 3 +-
.../endpoint-resolve-interceptor-preSra.java | 2 +-
...e-interceptor-with-endpointsbasedauth.java | 2 +-
...olve-interceptor-with-multiauthsigv4a.java | 2 +-
.../rules/endpoint-resolve-interceptor.java | 2 +-
.../DefaultAsyncPresignedUrlManager.java | 4 +-
.../AsyncPresignedUrlManager.java | 111 ++--
.../DefaultAsyncPresignedUrlManagerTest.java | 2 +-
.../AsyncPresignedUrlManagerTest.java | 522 ++++++++++++++++++
9 files changed, 608 insertions(+), 42 deletions(-)
create mode 100644 services/s3/src/test/java/software/amazon/awssdk/services/s3/presignedurl/AsyncPresignedUrlManagerTest.java
diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/rules/EndpointResolverInterceptorSpec.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/rules/EndpointResolverInterceptorSpec.java
index 726d61cdb99e..57d861ebb025 100644
--- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/rules/EndpointResolverInterceptorSpec.java
+++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/rules/EndpointResolverInterceptorSpec.java
@@ -255,7 +255,8 @@ private MethodSpec modifyHttpRequestMethod() {
b.addStatement("$T resolvedEndpoint = executionAttributes.getAttribute($T.RESOLVED_ENDPOINT)",
Endpoint.class, SdkInternalExecutionAttribute.class);
- b.beginControlFlow("if (resolvedEndpoint.headers().isEmpty())");
+ b.beginControlFlow("if (resolvedEndpoint == null || $T.isNullOrEmpty(resolvedEndpoint.headers()))",
+ CollectionUtils.class);
b.addStatement("return context.httpRequest()");
b.endControlFlow();
diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/rules/endpoint-resolve-interceptor-preSra.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/rules/endpoint-resolve-interceptor-preSra.java
index cf27b03ed0c3..e40fb151f74f 100644
--- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/rules/endpoint-resolve-interceptor-preSra.java
+++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/rules/endpoint-resolve-interceptor-preSra.java
@@ -110,7 +110,7 @@ public SdkRequest modifyRequest(Context.ModifyRequest context, ExecutionAttribut
@Override
public SdkHttpRequest modifyHttpRequest(Context.ModifyHttpRequest context, ExecutionAttributes executionAttributes) {
Endpoint resolvedEndpoint = executionAttributes.getAttribute(SdkInternalExecutionAttribute.RESOLVED_ENDPOINT);
- if (resolvedEndpoint.headers().isEmpty()) {
+ if (resolvedEndpoint == null || CollectionUtils.isNullOrEmpty(resolvedEndpoint.headers())) {
return context.httpRequest();
}
SdkHttpRequest.Builder httpRequestBuilder = context.httpRequest().toBuilder();
diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/rules/endpoint-resolve-interceptor-with-endpointsbasedauth.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/rules/endpoint-resolve-interceptor-with-endpointsbasedauth.java
index 6d6aa1dc2138..116ad513318d 100644
--- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/rules/endpoint-resolve-interceptor-with-endpointsbasedauth.java
+++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/rules/endpoint-resolve-interceptor-with-endpointsbasedauth.java
@@ -102,7 +102,7 @@ public SdkRequest modifyRequest(Context.ModifyRequest context, ExecutionAttribut
@Override
public SdkHttpRequest modifyHttpRequest(Context.ModifyHttpRequest context, ExecutionAttributes executionAttributes) {
Endpoint resolvedEndpoint = executionAttributes.getAttribute(SdkInternalExecutionAttribute.RESOLVED_ENDPOINT);
- if (resolvedEndpoint.headers().isEmpty()) {
+ if (resolvedEndpoint == null || CollectionUtils.isNullOrEmpty(resolvedEndpoint.headers())) {
return context.httpRequest();
}
SdkHttpRequest.Builder httpRequestBuilder = context.httpRequest().toBuilder();
diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/rules/endpoint-resolve-interceptor-with-multiauthsigv4a.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/rules/endpoint-resolve-interceptor-with-multiauthsigv4a.java
index 858d39fc7a69..dd81bd471965 100644
--- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/rules/endpoint-resolve-interceptor-with-multiauthsigv4a.java
+++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/rules/endpoint-resolve-interceptor-with-multiauthsigv4a.java
@@ -91,7 +91,7 @@ public SdkRequest modifyRequest(Context.ModifyRequest context, ExecutionAttribut
@Override
public SdkHttpRequest modifyHttpRequest(Context.ModifyHttpRequest context, ExecutionAttributes executionAttributes) {
Endpoint resolvedEndpoint = executionAttributes.getAttribute(SdkInternalExecutionAttribute.RESOLVED_ENDPOINT);
- if (resolvedEndpoint.headers().isEmpty()) {
+ if (resolvedEndpoint == null || CollectionUtils.isNullOrEmpty(resolvedEndpoint.headers())) {
return context.httpRequest();
}
SdkHttpRequest.Builder httpRequestBuilder = context.httpRequest().toBuilder();
diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/rules/endpoint-resolve-interceptor.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/rules/endpoint-resolve-interceptor.java
index abdfedab0455..547cbaca685f 100644
--- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/rules/endpoint-resolve-interceptor.java
+++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/rules/endpoint-resolve-interceptor.java
@@ -93,7 +93,7 @@ public SdkRequest modifyRequest(Context.ModifyRequest context, ExecutionAttribut
@Override
public SdkHttpRequest modifyHttpRequest(Context.ModifyHttpRequest context, ExecutionAttributes executionAttributes) {
Endpoint resolvedEndpoint = executionAttributes.getAttribute(SdkInternalExecutionAttribute.RESOLVED_ENDPOINT);
- if (resolvedEndpoint.headers().isEmpty()) {
+ if (resolvedEndpoint == null || CollectionUtils.isNullOrEmpty(resolvedEndpoint.headers())) {
return context.httpRequest();
}
SdkHttpRequest.Builder httpRequestBuilder = context.httpRequest().toBuilder();
diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/presignedurl/DefaultAsyncPresignedUrlManager.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/presignedurl/DefaultAsyncPresignedUrlManager.java
index 75c4e581befb..0e757581e25b 100644
--- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/presignedurl/DefaultAsyncPresignedUrlManager.java
+++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/presignedurl/DefaultAsyncPresignedUrlManager.java
@@ -98,7 +98,7 @@ public CompletableFuture getObject(
try {
apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "S3");
//TODO: Discuss if we need to change OPERATION_NAME as part of Surface API Review
- apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "GetObject");
+ apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "PresignedUrlGetObject");
Pair, CompletableFuture> pair =
AsyncResponseTransformerUtils.wrapWithEndOfStreamFuture(asyncResponseTransformer);
@@ -113,7 +113,7 @@ public CompletableFuture getObject(
CompletableFuture executeFuture = clientHandler.execute(
new ClientExecutionParams()
- .withOperationName("GetObject")
+ .withOperationName("PresignedUrlGetObject")
.withProtocolMetadata(protocolMetadata)
.withResponseHandler(responseHandler)
.withErrorResponseHandler(errorResponseHandler)
diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/presignedurl/AsyncPresignedUrlManager.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/presignedurl/AsyncPresignedUrlManager.java
index 6342213a679a..93333a684ff5 100644
--- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/presignedurl/AsyncPresignedUrlManager.java
+++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/presignedurl/AsyncPresignedUrlManager.java
@@ -15,14 +15,14 @@
package software.amazon.awssdk.services.s3.presignedurl;
+import java.nio.file.Path;
import java.util.concurrent.CompletableFuture;
+import java.util.function.Consumer;
import software.amazon.awssdk.annotations.SdkPublicApi;
import software.amazon.awssdk.core.async.AsyncResponseTransformer;
import software.amazon.awssdk.core.exception.SdkClientException;
import software.amazon.awssdk.services.s3.S3AsyncClient;
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
-import software.amazon.awssdk.services.s3.model.InvalidObjectStateException;
-import software.amazon.awssdk.services.s3.model.NoSuchKeyException;
import software.amazon.awssdk.services.s3.model.S3Exception;
import software.amazon.awssdk.services.s3.presignedurl.model.PresignedUrlGetObjectRequest;
@@ -36,46 +36,89 @@ public interface AsyncPresignedUrlManager {
*
* Downloads an S3 object asynchronously using a presigned URL.
*
+ *
*
- * This operation uses a presigned URL that contains all necessary authentication information, eliminating the
- * need for AWS credentials at request time. The presigned URL must be valid and not expired.
+ * This operation uses a presigned URL to download an object from Amazon S3. The presigned URL must be valid and not expired.
*
- *
- * Range Requests
- *
+ *
*
- * Supports partial object downloads using HTTP Range headers. Specify the range parameter
- * in the request to download only a portion of the object (e.g., "bytes=0-1023").
+ * To download a specific byte range of the object, use the range parameter in the request.
*
- *
- *
*
- * @param request The presigned URL request containing the URL and optional range parameters
- * @param responseTransformer Transforms the response to the desired return type. See
- * {@link software.amazon.awssdk.core.async.AsyncResponseTransformer} for pre-built
- * implementations like downloading to a file or converting to bytes.
+ * @param request The presigned URL request containing the URL and optional parameters
+ * @param responseTransformer Transforms the response to the desired return type
* @param The type of the transformed response
- * @return A {@link CompletableFuture} containing the transformed result of the AsyncResponseTransformer
- * @throws software.amazon.awssdk.services.s3.model.NoSuchKeyException The specified object does not exist
- * @throws software.amazon.awssdk.services.s3.model.InvalidObjectStateException Object is archived and must be restored before
- * retrieval
- * @throws software.amazon.awssdk.core.exception.SdkClientException If any client side error occurs such as
- * network failures or invalid presigned URL
- * @throws S3Exception Base class for all S3 service exceptions.
- * Unknown exceptions will be thrown as an
- * instance of this type.
+ * @return A {@link CompletableFuture} containing the transformed result
+ * @throws SdkClientException If any client side error occurs
+ * @throws S3Exception Base class for all S3 service exceptions
*/
default CompletableFuture getObject(PresignedUrlGetObjectRequest request,
- AsyncResponseTransformer responseTransformer) throws NoSuchKeyException,
- InvalidObjectStateException,
- SdkClientException,
- S3Exception {
+ AsyncResponseTransformer responseTransformer) {
throw new UnsupportedOperationException();
}
- // TODO: Add convenience methods :
- // - getObject(Consumer, AsyncResponseTransformer) - consumer-based request building
- // - getObject(PresignedUrlGetObjectRequest, Path) - direct file download
- // - getObject(Consumer, Path) - consumer + file download
-}
+ /**
+ *
+ * Downloads an S3 object asynchronously using a presigned URL with a consumer-based request builder.
+ *
+ *
+ *
+ * This is a convenience method that creates a {@link PresignedUrlGetObjectRequest} using the provided consumer.
+ *
+ *
+ * @param requestConsumer Consumer that will configure a {@link PresignedUrlGetObjectRequest.Builder}
+ * @param responseTransformer Transforms the response to the desired return type
+ * @param The type of the transformed response
+ * @return A {@link CompletableFuture} containing the transformed result
+ * @throws SdkClientException If any client side error occurs
+ * @throws S3Exception Base class for all S3 service exceptions
+ */
+ default CompletableFuture getObject(Consumer requestConsumer,
+ AsyncResponseTransformer responseTransformer) {
+ return getObject(PresignedUrlGetObjectRequest.builder().applyMutation(requestConsumer).build(), responseTransformer);
+ }
+
+ /**
+ *
+ * Downloads an S3 object asynchronously using a presigned URL and saves it to the specified file path.
+ *
+ *
+ *
+ * This is a convenience method that uses {@link AsyncResponseTransformer#toFile(Path)} to save the object
+ * directly to a file.
+ *
+ *
+ * @param request The presigned URL request containing the URL and optional parameters
+ * @param destinationPath The path where the downloaded object will be saved
+ * @return A {@link CompletableFuture} containing the {@link GetObjectResponse}
+ * @throws SdkClientException If any client side error occurs
+ * @throws S3Exception Base class for all S3 service exceptions
+ */
+ default CompletableFuture getObject(PresignedUrlGetObjectRequest request,
+ Path destinationPath) {
+ return getObject(request, AsyncResponseTransformer.toFile(destinationPath));
+ }
+
+ /**
+ *
+ * Downloads an S3 object asynchronously using a presigned URL with a consumer-based request builder
+ * and saves it to the specified file path.
+ *
+ *
+ *
+ * This is a convenience method that combines consumer-based request building with file-based response handling.
+ *
+ *
+ * @param requestConsumer Consumer that will configure a {@link PresignedUrlGetObjectRequest.Builder}
+ * @param destinationPath The path where the downloaded object will be saved
+ * @return A {@link CompletableFuture} containing the {@link GetObjectResponse}
+ * @throws SdkClientException If any client side error occurs
+ * @throws S3Exception Base class for all S3 service exceptions
+ */
+ default CompletableFuture getObject(Consumer requestConsumer,
+ Path destinationPath) {
+ return getObject(requestConsumer, AsyncResponseTransformer.toFile(destinationPath));
+ }
+}
\ No newline at end of file
diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/DefaultAsyncPresignedUrlManagerTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/DefaultAsyncPresignedUrlManagerTest.java
index b74ba0089189..e569287aa582 100644
--- a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/DefaultAsyncPresignedUrlManagerTest.java
+++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/DefaultAsyncPresignedUrlManagerTest.java
@@ -256,7 +256,7 @@ void given_AsyncPresignedUrlManager_when_ExecutingDifferentScenarios_then_Should
verify(mockPublisher).publish(metricsCaptor.capture());
MetricCollection capturedMetrics = metricsCaptor.getValue();
assertThat(capturedMetrics.metricValues(CoreMetric.SERVICE_ID)).contains("S3");
- assertThat(capturedMetrics.metricValues(CoreMetric.OPERATION_NAME)).contains("GetObject");
+ assertThat(capturedMetrics.metricValues(CoreMetric.OPERATION_NAME)).contains("PresignedUrlGetObject");
break;
}
}
diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/presignedurl/AsyncPresignedUrlManagerTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/presignedurl/AsyncPresignedUrlManagerTest.java
new file mode 100644
index 000000000000..3441b46d142c
--- /dev/null
+++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/presignedurl/AsyncPresignedUrlManagerTest.java
@@ -0,0 +1,522 @@
+/*
+ * 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.services.s3.presignedurl;
+
+import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
+import static com.github.tomakehurst.wiremock.client.WireMock.exactly;
+import static com.github.tomakehurst.wiremock.client.WireMock.get;
+import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor;
+import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
+import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
+import static com.github.tomakehurst.wiremock.client.WireMock.verify;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import java.net.URI;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+
+import com.github.tomakehurst.wiremock.http.Fault;
+import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo;
+import com.github.tomakehurst.wiremock.junit5.WireMockTest;
+import com.github.tomakehurst.wiremock.stubbing.Scenario;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import software.amazon.awssdk.core.ResponseBytes;
+import software.amazon.awssdk.core.async.AsyncResponseTransformer;
+import software.amazon.awssdk.core.exception.SdkClientException;
+import software.amazon.awssdk.services.s3.S3AsyncClient;
+import software.amazon.awssdk.services.s3.model.GetObjectResponse;
+import software.amazon.awssdk.services.s3.presignedurl.model.PresignedUrlGetObjectRequest;
+
+@WireMockTest
+public class AsyncPresignedUrlManagerTest {
+
+ private S3AsyncClient s3AsyncClient;
+ private AsyncPresignedUrlManager presignedUrlManager;
+
+ @TempDir
+ Path tempDir;
+
+ @BeforeEach
+ void setup(WireMockRuntimeInfo wireMockRuntimeInfo) {
+ s3AsyncClient = S3AsyncClient.builder()
+ .endpointOverride(URI.create(wireMockRuntimeInfo.getHttpBaseUrl()))
+ .build();
+ presignedUrlManager = s3AsyncClient.presignedUrlManager();
+ }
+
+ @AfterEach
+ void cleanup() {
+ if (s3AsyncClient != null) {
+ s3AsyncClient.close();
+ }
+ }
+
+ @Nested
+ class BasicFunctionality {
+ @Test
+ void givenS3AsyncClient_whenPresignedUrlManagerRequested_thenReturnsNonNullInstance() {
+ assertThat(presignedUrlManager).isNotNull();
+ }
+
+ @Test
+ void givenValidPresignedUrl_whenGetObjectCalled_thenReturnsExpectedContent(WireMockRuntimeInfo wireMockRuntimeInfo) throws Exception {
+ String testContent = "Hello world";
+ String testETag = "\"d6eb32081c822ed572b70567826d9d9d\"";
+ String presignedUrlPath = "/presigned-test-object";
+
+ stubSuccessResponse(presignedUrlPath, testContent, testETag);
+
+ URL presignedUrl = createPresignedUrl(wireMockRuntimeInfo, presignedUrlPath);
+ PresignedUrlGetObjectRequest request = createRequest(presignedUrl);
+
+ CompletableFuture> result =
+ presignedUrlManager.getObject(request, AsyncResponseTransformer.toBytes());
+
+ ResponseBytes response = result.get();
+ assertSuccessfulResponse(response, testContent, testETag);
+ }
+
+ @Test
+ void givenValidPresignedUrl_whenGetObjectWithConsumerBuilderCalled_thenReturnsExpectedContent(WireMockRuntimeInfo wireMockRuntimeInfo) throws Exception {
+ String testContent = "Hello consumer";
+ String testETag = "\"c1234567890abcdef1234567890abcdef\"";
+ String presignedUrlPath = "/presigned-test-consumer";
+
+ stubSuccessResponse(presignedUrlPath, testContent, testETag);
+
+ URL presignedUrl = createPresignedUrl(wireMockRuntimeInfo, presignedUrlPath);
+
+ CompletableFuture> result =
+ presignedUrlManager.getObject(
+ request -> request.presignedUrl(presignedUrl),
+ AsyncResponseTransformer.toBytes()
+ );
+
+ ResponseBytes response = result.get();
+ assertSuccessfulResponse(response, testContent, testETag);
+ }
+
+ @Test
+ void givenEmptyFile_whenGetObjectCalled_thenReturnsEmptyContent(WireMockRuntimeInfo wireMockRuntimeInfo) throws Exception {
+ String testContent = "";
+ String testETag = "\"empty-file-etag\"";
+ String presignedUrlPath = "/empty-file-memory";
+
+ // Using custom stubbing for empty file to include Content-Length header
+ stubFor(get(urlEqualTo(presignedUrlPath))
+ .willReturn(aResponse()
+ .withStatus(200)
+ .withHeader("ETag", testETag)
+ .withHeader("Content-Length", "0")
+ .withBody(testContent)));
+
+ URL presignedUrl = createPresignedUrl(wireMockRuntimeInfo, presignedUrlPath);
+ PresignedUrlGetObjectRequest request = createRequest(presignedUrl);
+
+ CompletableFuture> result =
+ presignedUrlManager.getObject(request, AsyncResponseTransformer.toBytes());
+
+ ResponseBytes response = result.get();
+ assertThat(response).isNotNull();
+ assertThat(response.asByteArray()).isEmpty();
+ assertThat(response.response().eTag()).isEqualTo(testETag);
+ }
+
+ @Test
+ void givenRangeRequest_whenGetObjectCalled_thenReturnsPartialContent(WireMockRuntimeInfo wireMockRuntimeInfo) throws Exception {
+ String expectedContent = "Hello";
+ String contentRange = "bytes 0-4/11";
+ String range = "bytes=0-4";
+ String testETag = "\"d6eb32081c822ed572b70567826d9d9d\"";
+ String presignedUrlPath = "/presigned-test-range";
+
+ // Custom stubbing for range request with 206 status
+ stubFor(get(urlEqualTo(presignedUrlPath))
+ .willReturn(aResponse()
+ .withStatus(206)
+ .withHeader("ETag", testETag)
+ .withHeader("Content-Range", contentRange)
+ .withBody(expectedContent)));
+
+ URL presignedUrl = createPresignedUrl(wireMockRuntimeInfo, presignedUrlPath);
+ PresignedUrlGetObjectRequest request = PresignedUrlGetObjectRequest.builder()
+ .presignedUrl(presignedUrl)
+ .range(range)
+ .build();
+
+ CompletableFuture> result =
+ presignedUrlManager.getObject(request, AsyncResponseTransformer.toBytes());
+
+ ResponseBytes response = result.get();
+ assertThat(response).isNotNull();
+ assertThat(response.asUtf8String()).isEqualTo(expectedContent);
+ }
+ }
+
+ @Nested
+ class FileDownloads {
+ @Test
+ void givenValidPresignedUrl_whenGetObjectToFileCalled_thenDownloadsFileWithExpectedContent(WireMockRuntimeInfo wireMockRuntimeInfo) throws Exception {
+ String testContent = "File content for download";
+ String testETag = generateRandomETag();
+ String presignedUrlPath = "/presigned-test-file";
+
+ stubSuccessResponse(presignedUrlPath, testContent, testETag);
+
+ URL presignedUrl = createPresignedUrl(wireMockRuntimeInfo, presignedUrlPath);
+ PresignedUrlGetObjectRequest request = createRequest(presignedUrl);
+
+ Path downloadFile = tempDir.resolve("download-" + UUID.randomUUID() + ".txt");
+
+ CompletableFuture result =
+ presignedUrlManager.getObject(request, downloadFile);
+
+ GetObjectResponse response = result.get();
+ assertThat(response).isNotNull();
+ assertThat(response.eTag()).isEqualTo(testETag);
+
+ String fileContent = new String(Files.readAllBytes(downloadFile), StandardCharsets.UTF_8);
+ assertThat(fileContent).isEqualTo(testContent);
+ }
+
+ @Test
+ void givenValidPresignedUrl_whenGetObjectToFileWithConsumerBuilderCalled_thenDownloadsFileWithExpectedContent(WireMockRuntimeInfo wireMockRuntimeInfo) throws Exception {
+ String testContent = "File content for consumer download";
+ String testETag = generateRandomETag();
+ String presignedUrlPath = "/presigned-test-consumer-file";
+
+ stubSuccessResponse(presignedUrlPath, testContent, testETag);
+
+ URL presignedUrl = createPresignedUrl(wireMockRuntimeInfo, presignedUrlPath);
+ Path downloadFile = tempDir.resolve("consumer-download-" + UUID.randomUUID() + ".txt");
+
+ CompletableFuture result =
+ presignedUrlManager.getObject(
+ request -> request.presignedUrl(presignedUrl),
+ downloadFile
+ );
+
+ GetObjectResponse response = result.get();
+ assertThat(response).isNotNull();
+ assertThat(response.eTag()).isEqualTo(testETag);
+
+ String fileContent = new String(Files.readAllBytes(downloadFile), StandardCharsets.UTF_8);
+ assertThat(fileContent).isEqualTo(testContent);
+ }
+
+ @Test
+ void givenEmptyFile_whenGetObjectToFileCalled_thenDownloadsEmptyFile(WireMockRuntimeInfo wireMockRuntimeInfo) throws Exception {
+ String testContent = "";
+ String testETag = "\"empty-file-etag\"";
+ String presignedUrlPath = "/empty-file";
+
+ // Custom stubbing for empty file to include Content-Length header
+ stubFor(get(urlEqualTo(presignedUrlPath))
+ .willReturn(aResponse()
+ .withStatus(200)
+ .withHeader("ETag", testETag)
+ .withHeader("Content-Length", "0")
+ .withBody(testContent)));
+
+ URL presignedUrl = createPresignedUrl(wireMockRuntimeInfo, presignedUrlPath);
+ PresignedUrlGetObjectRequest request = createRequest(presignedUrl);
+
+ Path downloadFile = tempDir.resolve("empty-file-" + UUID.randomUUID() + ".txt");
+ CompletableFuture result = presignedUrlManager.getObject(request, downloadFile);
+
+ GetObjectResponse response = result.get();
+ assertThat(response).isNotNull();
+ assertThat(response.eTag()).isEqualTo(testETag);
+ assertThat(Files.size(downloadFile)).isEqualTo(0);
+ }
+
+ @Test
+ void givenLargeFile_whenGetObjectCalled_thenDownloadsCompleteContent(WireMockRuntimeInfo wireMockRuntimeInfo) throws Exception {
+ int contentSize = 1024 * 1024 + 512 * 1024; // 1.5MB
+ byte[] largeContent = new byte[contentSize];
+ new Random().nextBytes(largeContent);
+
+ String testETag = "\"large-file-etag\"";
+ String presignedUrlPath = "/large-file";
+
+ // Custom stubbing for large file with Content-Length header
+ stubFor(get(urlEqualTo(presignedUrlPath))
+ .willReturn(aResponse()
+ .withStatus(200)
+ .withHeader("ETag", testETag)
+ .withHeader("Content-Length", String.valueOf(contentSize))
+ .withBody(largeContent)));
+
+ URL presignedUrl = createPresignedUrl(wireMockRuntimeInfo, presignedUrlPath);
+ PresignedUrlGetObjectRequest request = createRequest(presignedUrl);
+
+ Path downloadFile = tempDir.resolve("large-file-" + UUID.randomUUID() + ".bin");
+ CompletableFuture result = presignedUrlManager.getObject(request, downloadFile);
+
+ GetObjectResponse response = result.get();
+ assertThat(response).isNotNull();
+ assertThat(response.eTag()).isEqualTo(testETag);
+
+ assertThat(Files.size(downloadFile)).isEqualTo(contentSize);
+
+ byte[] downloadedContent = Files.readAllBytes(downloadFile);
+ assertThat(downloadedContent).isEqualTo(largeContent);
+ }
+ }
+
+ @Nested
+ class RetryBehavior {
+ @Test
+ void givenNetworkError_whenGetObjectCalled_thenRetriesAndEventuallySucceeds(WireMockRuntimeInfo wireMockRuntimeInfo) throws Exception {
+ String testContent = "Success after network error";
+ String testETag = "\"network-retry-etag\"";
+ String presignedUrlPath = "/network-error-retry";
+ String scenarioName = "network-error-scenario";
+
+ stubFor(get(urlEqualTo(presignedUrlPath))
+ .inScenario(scenarioName)
+ .whenScenarioStateIs(Scenario.STARTED)
+ .willReturn(aResponse()
+ .withFault(Fault.CONNECTION_RESET_BY_PEER))
+ .willSetStateTo("after-network-error"));
+
+ stubFor(get(urlEqualTo(presignedUrlPath))
+ .inScenario(scenarioName)
+ .whenScenarioStateIs("after-network-error")
+ .willReturn(aResponse()
+ .withStatus(200)
+ .withHeader("ETag", testETag)
+ .withBody(testContent)));
+
+ URL presignedUrl = createPresignedUrl(wireMockRuntimeInfo, presignedUrlPath);
+ PresignedUrlGetObjectRequest request = createRequest(presignedUrl);
+
+ CompletableFuture> result =
+ presignedUrlManager.getObject(request, AsyncResponseTransformer.toBytes());
+
+ ResponseBytes response = result.get();
+ assertSuccessfulResponse(response, testContent, testETag);
+
+ verify(exactly(2), getRequestedFor(urlEqualTo(presignedUrlPath)));
+ }
+
+ @Test
+ void givenTemporaryFailure_whenGetObjectCalled_thenRetriesAndSucceeds(WireMockRuntimeInfo wireMockRuntimeInfo) throws Exception {
+ String testContent = "Success after retry";
+ String testETag = "\"retry-success-etag\"";
+ String presignedUrlPath = "/retry-test";
+ String scenarioName = "retry-scenario";
+
+ stubFor(get(urlEqualTo(presignedUrlPath))
+ .inScenario(scenarioName)
+ .whenScenarioStateIs(Scenario.STARTED)
+ .willReturn(aResponse()
+ .withStatus(503)
+ .withBody("Service Unavailable"))
+ .willSetStateTo("retry"));
+
+ stubFor(get(urlEqualTo(presignedUrlPath))
+ .inScenario(scenarioName)
+ .whenScenarioStateIs("retry")
+ .willReturn(aResponse()
+ .withStatus(200)
+ .withHeader("ETag", testETag)
+ .withBody(testContent)));
+
+ URL presignedUrl = createPresignedUrl(wireMockRuntimeInfo, presignedUrlPath);
+ PresignedUrlGetObjectRequest request = createRequest(presignedUrl);
+
+ CompletableFuture> result =
+ presignedUrlManager.getObject(request, AsyncResponseTransformer.toBytes());
+
+ ResponseBytes response = result.get();
+ assertSuccessfulResponse(response, testContent, testETag);
+
+ verify(exactly(2), getRequestedFor(urlEqualTo(presignedUrlPath)));
+ }
+ }
+
+ @Nested
+ class ErrorScenarios {
+ @Test
+ void givenMalformedResponse_whenGetObjectCalled_thenCompletableFutureCompletesExceptionally(WireMockRuntimeInfo wireMockRuntimeInfo) throws Exception {
+ String presignedUrlPath = "/malformed-response";
+ stubFor(get(urlEqualTo(presignedUrlPath))
+ .willReturn(aResponse()
+ .withFault(Fault.CONNECTION_RESET_BY_PEER)));
+
+ URL presignedUrl = createPresignedUrl(wireMockRuntimeInfo, presignedUrlPath);
+ PresignedUrlGetObjectRequest request = createRequest(presignedUrl);
+
+ CompletableFuture> result =
+ presignedUrlManager.getObject(request, AsyncResponseTransformer.toBytes());
+ try {
+ result.get();
+ throw new AssertionError("Expected exception was not thrown");
+ } catch (ExecutionException e) {
+ assertThat(e.getCause()).isInstanceOf(SdkClientException.class);
+ }
+ }
+
+ @Test
+ void givenNotFoundError_whenGetObjectCalled_thenCompletableFutureCompletesExceptionally(WireMockRuntimeInfo wireMockRuntimeInfo) {
+ String urlPath = "/nonexistent-key";
+
+ stubFor(get(urlEqualTo(urlPath))
+ .willReturn(aResponse()
+ .withStatus(404)
+ .withBody("NoSuchKey ")));
+
+ URL presignedUrl;
+ try {
+ presignedUrl = new URL(wireMockRuntimeInfo.getHttpBaseUrl() + urlPath);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+
+ PresignedUrlGetObjectRequest request = PresignedUrlGetObjectRequest.builder()
+ .presignedUrl(presignedUrl)
+ .build();
+
+ CompletableFuture> result =
+ presignedUrlManager.getObject(request, AsyncResponseTransformer.toBytes());
+
+ assertThatThrownBy(() -> result.get())
+ .isInstanceOf(ExecutionException.class);
+ }
+
+ @Test
+ void givenAccessDeniedError_whenGetObjectCalled_thenCompletableFutureCompletesExceptionally(WireMockRuntimeInfo wireMockRuntimeInfo) {
+ String urlPath = "/forbidden-key";
+
+ stubFor(get(urlEqualTo(urlPath))
+ .willReturn(aResponse()
+ .withStatus(403)
+ .withBody("AccessDenied ")));
+
+ URL presignedUrl;
+ try {
+ presignedUrl = new URL(wireMockRuntimeInfo.getHttpBaseUrl() + urlPath);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+
+ PresignedUrlGetObjectRequest request = PresignedUrlGetObjectRequest.builder()
+ .presignedUrl(presignedUrl)
+ .build();
+
+ CompletableFuture> result =
+ presignedUrlManager.getObject(request, AsyncResponseTransformer.toBytes());
+
+ assertThatThrownBy(() -> result.get())
+ .isInstanceOf(ExecutionException.class);
+ }
+ }
+
+ @Nested
+ class ConcurrentOperations {
+ @Test
+ void givenMultipleRequests_whenGetObjectCalledConcurrently_thenAllDownloadsComplete(WireMockRuntimeInfo wireMockRuntimeInfo) throws Exception {
+ int concurrentRequests = 5;
+ List contents = new ArrayList<>();
+ List eTags = new ArrayList<>();
+ List paths = new ArrayList<>();
+
+ for (int i = 0; i < concurrentRequests; i++) {
+ String content = "Content for concurrent download " + i;
+ String eTag = "\"concurrent-etag-" + i + "\"";
+ String path = "/concurrent-" + i;
+
+ contents.add(content);
+ eTags.add(eTag);
+ paths.add(path);
+
+ stubSuccessResponse(path, content, eTag);
+ }
+
+ List>> futures = new ArrayList<>();
+ for (int i = 0; i < concurrentRequests; i++) {
+ URL presignedUrl = createPresignedUrl(wireMockRuntimeInfo, paths.get(i));
+ PresignedUrlGetObjectRequest request = createRequest(presignedUrl);
+
+ futures.add(presignedUrlManager.getObject(request, AsyncResponseTransformer.toBytes()));
+ }
+
+ CompletableFuture allFutures = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
+ allFutures.get();
+
+ for (int i = 0; i < concurrentRequests; i++) {
+ ResponseBytes response = futures.get(i).get();
+ assertSuccessfulResponse(response, contents.get(i), eTags.get(i));
+ }
+ }
+ }
+
+ // Helper methods
+ private URL createPresignedUrl(WireMockRuntimeInfo wireMockRuntimeInfo, String path) {
+ try {
+ return new URL(wireMockRuntimeInfo.getHttpBaseUrl() + path);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private PresignedUrlGetObjectRequest createRequest(URL presignedUrl) {
+ return PresignedUrlGetObjectRequest.builder()
+ .presignedUrl(presignedUrl)
+ .build();
+ }
+
+ private String generateRandomETag() {
+ return "\"" + UUID.randomUUID().toString().replace("-", "") + "\"";
+ }
+
+ private void stubSuccessResponse(String path, String content, String eTag) {
+ stubFor(get(urlEqualTo(path))
+ .willReturn(aResponse()
+ .withStatus(200)
+ .withHeader("ETag", eTag)
+ .withBody(content)));
+ }
+
+ private void stubErrorResponse(String path, int statusCode, String body) {
+ stubFor(get(urlEqualTo(path))
+ .willReturn(aResponse()
+ .withStatus(statusCode)
+ .withBody(body)));
+ }
+
+ private void assertSuccessfulResponse(ResponseBytes response, String expectedContent, String expectedETag) {
+ assertThat(response).isNotNull();
+ assertThat(response.asUtf8String()).isEqualTo(expectedContent);
+ assertThat(response.response().eTag()).isEqualTo(expectedETag);
+ }
+
+}
\ No newline at end of file
From a2177ef7e2deb0d7c1fae233b5c6ad0b22751d6d Mon Sep 17 00:00:00 2001
From: jencymaryjoseph <35571282+jencymaryjoseph@users.noreply.github.com>
Date: Wed, 23 Jul 2025 13:39:36 -0700
Subject: [PATCH 08/30] Rename PresignedUrlManager to PresignedUrlExtension
following API review (#6286)
* surface api name changes
* rename files, remove consumer builder from downloadrequest
* some nitpicks
* renamed variables
---
.../customization/CustomizationConfig.java | 12 ++--
.../awssdk/codegen/poet/PoetExtension.java | 4 +-
.../codegen/poet/client/AsyncClientClass.java | 10 +--
.../poet/client/AsyncClientInterface.java | 14 ++--
.../client/DelegatingAsyncClientClass.java | 4 +-
.../awssdk/codegen/poet/ClientTestModels.java | 2 +-
.../poet/client/AsyncClientClassTest.java | 8 +--
.../poet/client/AsyncClientInterfaceTest.java | 6 +-
.../DelegatingAsyncClientClassTest.java | 6 +-
.../c2j/presignedurl/customization.config | 2 +-
...tract-async-client-class-presignedurl.java | 8 +--
...n-async-client-interface-presignedurl.java | 6 +-
.../poet/client/test-presignedurl-async.java | 8 +--
.../internal/crt/DefaultS3CrtAsyncClient.java | 7 ++
.../multipart/MultipartS3AsyncClient.java | 7 ++
...=> DefaultAsyncPresignedUrlExtension.java} | 31 ++++-----
...resignedUrlDownloadRequestMarshaller.java} | 18 ++---
...> PresignedUrlDownloadRequestWrapper.java} | 20 +++---
...r.java => AsyncPresignedUrlExtension.java} | 22 +++---
....java => PresignedUrlDownloadRequest.java} | 35 ++++------
.../codegen-resources/customization.config | 2 +-
...efaultAsyncPresignedUrlExtensionTest.java} | 52 +++++++-------
...gnedUrlDownloadRequestMarshallerTest.java} | 26 +++----
...esignedUrlDownloadRequestWrapperTest.java} | 12 ++--
...va => AsyncPresignedUrlExtensionTest.java} | 68 +++++++++----------
...a => PresignedUrlDownloadRequestTest.java} | 22 +++---
26 files changed, 207 insertions(+), 205 deletions(-)
rename services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/presignedurl/{DefaultAsyncPresignedUrlManager.java => DefaultAsyncPresignedUrlExtension.java} (87%)
rename services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/presignedurl/{PresignedUrlGetObjectRequestMarshaller.java => PresignedUrlDownloadRequestMarshaller.java} (78%)
rename services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/presignedurl/model/{PresignedUrlGetObjectRequestWrapper.java => PresignedUrlDownloadRequestWrapper.java} (86%)
rename services/s3/src/main/java/software/amazon/awssdk/services/s3/presignedurl/{AsyncPresignedUrlManager.java => AsyncPresignedUrlExtension.java} (90%)
rename services/s3/src/main/java/software/amazon/awssdk/services/s3/presignedurl/model/{PresignedUrlGetObjectRequest.java => PresignedUrlDownloadRequest.java} (77%)
rename services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/{DefaultAsyncPresignedUrlManagerTest.java => DefaultAsyncPresignedUrlExtensionTest.java} (88%)
rename services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/{PresignedUrlGetObjectRequestMarshallerTest.java => PresignedUrlDownloadRequestMarshallerTest.java} (88%)
rename services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/model/{PresignedUrlGetObjectRequestWrapperTest.java => PresignedUrlDownloadRequestWrapperTest.java} (87%)
rename services/s3/src/test/java/software/amazon/awssdk/services/s3/presignedurl/{AsyncPresignedUrlManagerTest.java => AsyncPresignedUrlExtensionTest.java} (89%)
rename services/s3/src/test/java/software/amazon/awssdk/services/s3/presignedurl/model/{PresignedUrlGetObjectRequestTest.java => PresignedUrlDownloadRequestTest.java} (83%)
diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/model/config/customization/CustomizationConfig.java b/codegen/src/main/java/software/amazon/awssdk/codegen/model/config/customization/CustomizationConfig.java
index 4a7477b40290..805bf02dce20 100644
--- a/codegen/src/main/java/software/amazon/awssdk/codegen/model/config/customization/CustomizationConfig.java
+++ b/codegen/src/main/java/software/amazon/awssdk/codegen/model/config/customization/CustomizationConfig.java
@@ -352,9 +352,9 @@ public class CustomizationConfig {
private boolean batchManagerSupported;
/**
- * A boolean flag to indicate if Presigned URL Manager is supported.
+ * A boolean flag to indicate if Presigned URL Extension is supported.
*/
- private boolean presignedUrlManagerSupported;
+ private boolean presignedUrlExtensionSupported;
/**
* A boolean flag to indicate if the fast unmarshaller code path is enabled.
@@ -951,12 +951,12 @@ public void setBatchManagerSupported(boolean batchManagerSupported) {
this.batchManagerSupported = batchManagerSupported;
}
- public boolean getPresignedUrlManagerSupported() {
- return presignedUrlManagerSupported;
+ public boolean getPresignedUrlExtensionSupported() {
+ return presignedUrlExtensionSupported;
}
- public void setPresignedUrlManagerSupported(boolean presignedUrlManagerSupported) {
- this.presignedUrlManagerSupported = presignedUrlManagerSupported;
+ public void setPresignedUrlExtensionSupported(boolean presignedUrlExtensionSupported) {
+ this.presignedUrlExtensionSupported = presignedUrlExtensionSupported;
}
public boolean getEnableFastUnmarshaller() {
diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/PoetExtension.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/PoetExtension.java
index 54888ce41b57..9e4fea7f6b31 100644
--- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/PoetExtension.java
+++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/PoetExtension.java
@@ -210,7 +210,7 @@ public ClassName getBatchManagerAsyncInterface() {
model.getMetadata().getServiceName() + "AsyncBatchManager");
}
- public ClassName getPresignedUrlManagerAsyncInterface() {
- return ClassName.get(model.getMetadata().getFullPresignedUrlPackageName(), "AsyncPresignedUrlManager");
+ public ClassName getPresignedUrlExtensionAsyncInterface() {
+ return ClassName.get(model.getMetadata().getFullPresignedUrlPackageName(), "AsyncPresignedUrlExtension");
}
}
diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/AsyncClientClass.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/AsyncClientClass.java
index d29c3124475b..30014b630453 100644
--- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/AsyncClientClass.java
+++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/AsyncClientClass.java
@@ -540,12 +540,12 @@ protected void addBatchManagerMethod(Builder type) {
}
@Override
- protected void addPresignedUrlManagerMethod(Builder type) {
- ClassName returnType = poetExtensions.getPresignedUrlManagerAsyncInterface();
+ protected void addPresignedUrlExtensionMethod(Builder type) {
+ ClassName returnType = poetExtensions.getPresignedUrlExtensionAsyncInterface();
String internalPresignedUrlPackage = model.getMetadata().getFullInternalPackageName() + ".presignedurl";
- ClassName implClass = ClassName.get(internalPresignedUrlPackage, "DefaultAsyncPresignedUrlManager");
+ ClassName implClass = ClassName.get(internalPresignedUrlPackage, "DefaultAsyncPresignedUrlExtension");
- MethodSpec presignedUrlManager = MethodSpec.methodBuilder("presignedUrlManager")
+ MethodSpec presignedUrlExtension = MethodSpec.methodBuilder("presignedUrlExtension")
.addModifiers(PUBLIC)
.addAnnotation(Override.class)
.returns(returnType)
@@ -556,7 +556,7 @@ protected void addPresignedUrlManagerMethod(Builder type) {
implClass)
.build();
- type.addMethod(presignedUrlManager);
+ type.addMethod(presignedUrlExtension);
}
private MethodSpec resolveMetricPublishersMethod() {
diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/AsyncClientInterface.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/AsyncClientInterface.java
index ab4e9223ff40..fe6d40015703 100644
--- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/AsyncClientInterface.java
+++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/AsyncClientInterface.java
@@ -99,8 +99,8 @@ public TypeSpec poetSpec() {
if (model.getCustomizationConfig().getBatchManagerSupported()) {
addBatchManagerMethod(result);
}
- if (model.getCustomizationConfig().getPresignedUrlManagerSupported()) {
- addPresignedUrlManagerMethod(result);
+ if (model.getCustomizationConfig().getPresignedUrlExtensionSupported()) {
+ addPresignedUrlExtensionMethod(result);
}
result.addMethod(serviceClientConfigMethod());
addAdditionalMethods(result);
@@ -178,14 +178,14 @@ protected void addBatchManagerMethod(TypeSpec.Builder type) {
type.addMethod(batchManagerOperationBody(builder).build());
}
- protected void addPresignedUrlManagerMethod(TypeSpec.Builder type) {
- ClassName returnType = poetExtensions.getPresignedUrlManagerAsyncInterface();
- MethodSpec.Builder builder = MethodSpec.methodBuilder("presignedUrlManager")
+ protected void addPresignedUrlExtensionMethod(TypeSpec.Builder type) {
+ ClassName returnType = poetExtensions.getPresignedUrlExtensionAsyncInterface();
+ MethodSpec.Builder builder = MethodSpec.methodBuilder("presignedUrlExtension")
.addModifiers(PUBLIC)
.returns(returnType)
.addJavadoc("Creates an instance of {@link $T} object with the "
+ "configuration set on this client.", returnType);
- type.addMethod(presignedUrlManagerOperationBody(builder).build());
+ type.addMethod(presignedUrlExtensionOperationBody(builder).build());
}
@Override
@@ -564,7 +564,7 @@ protected MethodSpec.Builder batchManagerOperationBody(MethodSpec.Builder builde
.addStatement("throw new $T()", UnsupportedOperationException.class);
}
- protected MethodSpec.Builder presignedUrlManagerOperationBody(MethodSpec.Builder builder) {
+ protected MethodSpec.Builder presignedUrlExtensionOperationBody(MethodSpec.Builder builder) {
return builder.addModifiers(DEFAULT, PUBLIC)
.addStatement("throw new $T()", UnsupportedOperationException.class);
}
diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/DelegatingAsyncClientClass.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/DelegatingAsyncClientClass.java
index be0239e00808..3fd5fcec9f51 100644
--- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/DelegatingAsyncClientClass.java
+++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/DelegatingAsyncClientClass.java
@@ -224,7 +224,7 @@ protected MethodSpec.Builder batchManagerOperationBody(MethodSpec.Builder builde
}
@Override
- protected MethodSpec.Builder presignedUrlManagerOperationBody(MethodSpec.Builder builder) {
- return builder.addAnnotation(Override.class).addStatement("return delegate.presignedUrlManager()");
+ protected MethodSpec.Builder presignedUrlExtensionOperationBody(MethodSpec.Builder builder) {
+ return builder.addAnnotation(Override.class).addStatement("return delegate.presignedUrlExtension()");
}
}
diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/ClientTestModels.java b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/ClientTestModels.java
index 82a80e58652b..7426688dd92d 100644
--- a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/ClientTestModels.java
+++ b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/ClientTestModels.java
@@ -606,7 +606,7 @@ public static IntermediateModel batchManagerModels() {
return new IntermediateModelBuilder(models).build();
}
- public static IntermediateModel presignedUrlManagerModels() {
+ public static IntermediateModel presignedUrlExtensionModels() {
File serviceModel = new File(ClientTestModels.class.getResource("client/c2j/presignedurl/service-2.json").getFile());
File customizationModel = new File(ClientTestModels.class.getResource("client/c2j/presignedurl/customization.config").getFile());
diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/client/AsyncClientClassTest.java b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/client/AsyncClientClassTest.java
index 7a7c74375cb9..ab3bfeb68c92 100644
--- a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/client/AsyncClientClassTest.java
+++ b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/client/AsyncClientClassTest.java
@@ -24,7 +24,7 @@
import static software.amazon.awssdk.codegen.poet.ClientTestModels.customPackageModels;
import static software.amazon.awssdk.codegen.poet.ClientTestModels.endpointDiscoveryModels;
import static software.amazon.awssdk.codegen.poet.ClientTestModels.opsWithSigv4a;
-import static software.amazon.awssdk.codegen.poet.ClientTestModels.presignedUrlManagerModels;
+import static software.amazon.awssdk.codegen.poet.ClientTestModels.presignedUrlExtensionModels;
import static software.amazon.awssdk.codegen.poet.ClientTestModels.serviceWithCustomContextParamsModels;
import static software.amazon.awssdk.codegen.poet.ClientTestModels.queryServiceModels;
import static software.amazon.awssdk.codegen.poet.ClientTestModels.restJsonServiceModels;
@@ -105,9 +105,9 @@ public void asyncClientBatchManager() {
}
@Test
- public void asyncClientPresignedUrlManager() {
- ClassSpec asyncClientPresignedUrlManager = createAsyncClientClass(presignedUrlManagerModels());
- assertThat(asyncClientPresignedUrlManager, generatesTo("test-presignedurl-async.java"));
+ public void asyncClientPresignedUrlExtension() {
+ ClassSpec asyncClientPresignedUrlExtension = createAsyncClientClass(presignedUrlExtensionModels());
+ assertThat(asyncClientPresignedUrlExtension, generatesTo("test-presignedurl-async.java"));
}
@Test
diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/client/AsyncClientInterfaceTest.java b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/client/AsyncClientInterfaceTest.java
index 1a2f18183e8d..aae45702e837 100644
--- a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/client/AsyncClientInterfaceTest.java
+++ b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/client/AsyncClientInterfaceTest.java
@@ -17,7 +17,7 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static software.amazon.awssdk.codegen.poet.ClientTestModels.batchManagerModels;
-import static software.amazon.awssdk.codegen.poet.ClientTestModels.presignedUrlManagerModels;
+import static software.amazon.awssdk.codegen.poet.ClientTestModels.presignedUrlExtensionModels;
import static software.amazon.awssdk.codegen.poet.ClientTestModels.restJsonServiceModels;
import static software.amazon.awssdk.codegen.poet.PoetMatchers.generatesTo;
@@ -38,8 +38,8 @@ public void asyncClientInterfaceWithBatchManager() {
}
@Test
- public void asyncClientInterfaceWithPresignedUrlManager() {
- ClassSpec asyncClientInterface = new AsyncClientInterface(presignedUrlManagerModels());
+ public void asyncClientInterfaceWithPresignedUrlExtension() {
+ ClassSpec asyncClientInterface = new AsyncClientInterface(presignedUrlExtensionModels());
assertThat(asyncClientInterface, generatesTo("test-json-async-client-interface-presignedurl.java"));
}
}
diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/client/DelegatingAsyncClientClassTest.java b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/client/DelegatingAsyncClientClassTest.java
index a77cb67ab727..a9af3d6a8b3b 100644
--- a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/client/DelegatingAsyncClientClassTest.java
+++ b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/client/DelegatingAsyncClientClassTest.java
@@ -16,7 +16,7 @@
package software.amazon.awssdk.codegen.poet.client;
import static org.hamcrest.MatcherAssert.assertThat;
-import static software.amazon.awssdk.codegen.poet.ClientTestModels.presignedUrlManagerModels;
+import static software.amazon.awssdk.codegen.poet.ClientTestModels.presignedUrlExtensionModels;
import static software.amazon.awssdk.codegen.poet.ClientTestModels.restJsonServiceModels;
import static software.amazon.awssdk.codegen.poet.PoetMatchers.generatesTo;
@@ -31,9 +31,9 @@ public void delegatingAsyncClientClass() {
}
@Test
- public void delegatingAsyncClientClassWithPresignedUrlManager() {
+ public void delegatingAsyncClientClassWithPresignedUrlExtension() {
DelegatingAsyncClientClass asyncClientDecoratorAbstractClass =
- new DelegatingAsyncClientClass(presignedUrlManagerModels());
+ new DelegatingAsyncClientClass(presignedUrlExtensionModels());
assertThat(asyncClientDecoratorAbstractClass, generatesTo("test-abstract-async-client-class-presignedurl.java"));
}
}
diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/presignedurl/customization.config b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/presignedurl/customization.config
index cfc5b2874b7a..17d6c2306501 100644
--- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/presignedurl/customization.config
+++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/presignedurl/customization.config
@@ -1,3 +1,3 @@
{
- "presignedUrlManagerSupported": true
+ "presignedUrlExtensionSupported": true
}
\ No newline at end of file
diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-abstract-async-client-class-presignedurl.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-abstract-async-client-class-presignedurl.java
index 715c56f752a7..7e5984afccf7 100644
--- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-abstract-async-client-class-presignedurl.java
+++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-abstract-async-client-class-presignedurl.java
@@ -8,7 +8,7 @@
import software.amazon.awssdk.services.json.model.APostOperationRequest;
import software.amazon.awssdk.services.json.model.APostOperationResponse;
import software.amazon.awssdk.services.json.model.JsonRequest;
-import software.amazon.awssdk.services.json.presignedurl.AsyncPresignedUrlManager;
+import software.amazon.awssdk.services.json.presignedurl.AsyncPresignedUrlExtension;
import software.amazon.awssdk.utils.Validate;
@Generated("software.amazon.awssdk:codegen")
@@ -51,11 +51,11 @@ public CompletableFuture aPostOperation(APostOperationRe
}
/**
- * Creates an instance of {@link AsyncPresignedUrlManager} object with the configuration set on this client.
+ * Creates an instance of {@link AsyncPresignedUrlExtension} object with the configuration set on this client.
*/
@Override
- public AsyncPresignedUrlManager presignedUrlManager() {
- return delegate.presignedUrlManager();
+ public AsyncPresignedUrlExtension presignedUrlExtension() {
+ return delegate.presignedUrlExtension();
}
@Override
diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-json-async-client-interface-presignedurl.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-json-async-client-interface-presignedurl.java
index 631198b323bd..4b5bfc7eb400 100644
--- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-json-async-client-interface-presignedurl.java
+++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-json-async-client-interface-presignedurl.java
@@ -8,7 +8,7 @@
import software.amazon.awssdk.awscore.AwsClient;
import software.amazon.awssdk.services.json.model.APostOperationRequest;
import software.amazon.awssdk.services.json.model.APostOperationResponse;
-import software.amazon.awssdk.services.json.presignedurl.AsyncPresignedUrlManager;
+import software.amazon.awssdk.services.json.presignedurl.AsyncPresignedUrlExtension;
/**
* Service client for accessing Json Service asynchronously. This can be created using the static {@link #builder()}
@@ -94,9 +94,9 @@ default CompletableFuture aPostOperation(Consumer aPostOperation(APostOperationRe
}
@Override
- public AsyncPresignedUrlManager presignedUrlManager() {
- return new DefaultAsyncPresignedUrlManager(clientHandler, protocolFactory, clientConfiguration, protocolMetadata);
+ public AsyncPresignedUrlExtension presignedUrlExtension() {
+ return new DefaultAsyncPresignedUrlExtension(clientHandler, protocolFactory, clientConfiguration, protocolMetadata);
}
@Override
diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/crt/DefaultS3CrtAsyncClient.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/crt/DefaultS3CrtAsyncClient.java
index e49abb7126e5..eb32823f8deb 100644
--- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/crt/DefaultS3CrtAsyncClient.java
+++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/crt/DefaultS3CrtAsyncClient.java
@@ -80,6 +80,7 @@
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
import software.amazon.awssdk.services.s3.model.PutObjectResponse;
+import software.amazon.awssdk.services.s3.presignedurl.AsyncPresignedUrlExtension;
import software.amazon.awssdk.utils.AttributeMap;
import software.amazon.awssdk.utils.CollectionUtils;
import software.amazon.awssdk.utils.Validate;
@@ -501,4 +502,10 @@ private static void validateCrtInClassPath() {
+ "on the classpath.", e);
}
}
+
+ @Override
+ public AsyncPresignedUrlExtension presignedUrlExtension() {
+ // TODO: Implement presigned URL extension support for CRT client
+ throw new UnsupportedOperationException("Presigned URL extension is not supported for CRT client");
+ }
}
diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/multipart/MultipartS3AsyncClient.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/multipart/MultipartS3AsyncClient.java
index 499f36a6dd1b..bcb370e8286c 100644
--- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/multipart/MultipartS3AsyncClient.java
+++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/multipart/MultipartS3AsyncClient.java
@@ -35,6 +35,7 @@
import software.amazon.awssdk.services.s3.model.PutObjectResponse;
import software.amazon.awssdk.services.s3.model.S3Request;
import software.amazon.awssdk.services.s3.multipart.MultipartConfiguration;
+import software.amazon.awssdk.services.s3.presignedurl.AsyncPresignedUrlExtension;
import software.amazon.awssdk.utils.Validate;
/**
@@ -111,4 +112,10 @@ protected CompletableFuture invokeOperat
};
return new MultipartS3AsyncClient(clientWithUserAgent, multipartConfiguration, checksumEnabled);
}
+
+ @Override
+ public AsyncPresignedUrlExtension presignedUrlExtension() {
+ // TODO: Implement presigned URL extension support for multipart client
+ throw new UnsupportedOperationException("Presigned URL extension is not supported for multipart client");
+ }
}
diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/presignedurl/DefaultAsyncPresignedUrlManager.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/presignedurl/DefaultAsyncPresignedUrlExtension.java
similarity index 87%
rename from services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/presignedurl/DefaultAsyncPresignedUrlManager.java
rename to services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/presignedurl/DefaultAsyncPresignedUrlExtension.java
index 0e757581e25b..db7515b24b54 100644
--- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/presignedurl/DefaultAsyncPresignedUrlManager.java
+++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/presignedurl/DefaultAsyncPresignedUrlExtension.java
@@ -44,22 +44,22 @@
import software.amazon.awssdk.metrics.NoOpMetricCollector;
import software.amazon.awssdk.protocols.xml.AwsS3ProtocolFactory;
import software.amazon.awssdk.protocols.xml.XmlOperationMetadata;
-import software.amazon.awssdk.services.s3.internal.presignedurl.model.PresignedUrlGetObjectRequestWrapper;
+import software.amazon.awssdk.services.s3.internal.presignedurl.model.PresignedUrlDownloadRequestWrapper;
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
import software.amazon.awssdk.services.s3.model.InvalidObjectStateException;
import software.amazon.awssdk.services.s3.model.NoSuchKeyException;
import software.amazon.awssdk.services.s3.model.S3Exception;
-import software.amazon.awssdk.services.s3.presignedurl.AsyncPresignedUrlManager;
-import software.amazon.awssdk.services.s3.presignedurl.model.PresignedUrlGetObjectRequest;
+import software.amazon.awssdk.services.s3.presignedurl.AsyncPresignedUrlExtension;
+import software.amazon.awssdk.services.s3.presignedurl.model.PresignedUrlDownloadRequest;
import software.amazon.awssdk.utils.CompletableFutureUtils;
import software.amazon.awssdk.utils.Pair;
/**
- * Default implementation of {@link AsyncPresignedUrlManager} for executing S3 operations asynchronously using presigned URLs.
+ * Default implementation of {@link AsyncPresignedUrlExtension} for executing S3 operations asynchronously using presigned URLs.
*/
@SdkInternalApi
-public final class DefaultAsyncPresignedUrlManager implements AsyncPresignedUrlManager {
- private static final Logger log = LoggerFactory.getLogger(DefaultAsyncPresignedUrlManager.class);
+public final class DefaultAsyncPresignedUrlExtension implements AsyncPresignedUrlExtension {
+ private static final Logger log = LoggerFactory.getLogger(DefaultAsyncPresignedUrlExtension.class);
private final AsyncClientHandler clientHandler;
private final AwsS3ProtocolFactory protocolFactory;
@@ -67,7 +67,7 @@ public final class DefaultAsyncPresignedUrlManager implements AsyncPresignedUrlM
private final List metricPublishers;
private final AwsProtocolMetadata protocolMetadata;
- public DefaultAsyncPresignedUrlManager(AsyncClientHandler clientHandler,
+ public DefaultAsyncPresignedUrlExtension(AsyncClientHandler clientHandler,
AwsS3ProtocolFactory protocolFactory,
SdkClientConfiguration clientConfiguration,
AwsProtocolMetadata protocolMetadata) {
@@ -82,14 +82,14 @@ public DefaultAsyncPresignedUrlManager(AsyncClientHandler clientHandler,
@Override
public CompletableFuture getObject(
- PresignedUrlGetObjectRequest presignedUrlGetObjectRequest,
+ PresignedUrlDownloadRequest presignedUrlDownloadRequest,
AsyncResponseTransformer asyncResponseTransformer)
throws NoSuchKeyException, InvalidObjectStateException,
AwsServiceException, SdkClientException, S3Exception {
- PresignedUrlGetObjectRequestWrapper internalRequest = PresignedUrlGetObjectRequestWrapper.builder()
- .url(presignedUrlGetObjectRequest.presignedUrl())
- .range(presignedUrlGetObjectRequest.range())
+ PresignedUrlDownloadRequestWrapper internalRequest = PresignedUrlDownloadRequestWrapper.builder()
+ .url(presignedUrlDownloadRequest.presignedUrl())
+ .range(presignedUrlDownloadRequest.range())
.build();
MetricCollector apiCallMetricCollector = metricPublishers.isEmpty() ?
@@ -97,8 +97,7 @@ public CompletableFuture getObject(
try {
apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "S3");
- //TODO: Discuss if we need to change OPERATION_NAME as part of Surface API Review
- apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "PresignedUrlGetObject");
+ apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "PresignedUrlDownload");
Pair, CompletableFuture> pair =
AsyncResponseTransformerUtils.wrapWithEndOfStreamFuture(asyncResponseTransformer);
@@ -112,8 +111,8 @@ public CompletableFuture getObject(
HttpResponseHandler errorResponseHandler = protocolFactory.createErrorResponseHandler();
CompletableFuture executeFuture = clientHandler.execute(
- new ClientExecutionParams()
- .withOperationName("PresignedUrlGetObject")
+ new ClientExecutionParams()
+ .withOperationName("PresignedUrlDownload")
.withProtocolMetadata(protocolMetadata)
.withResponseHandler(responseHandler)
.withErrorResponseHandler(errorResponseHandler)
@@ -122,7 +121,7 @@ public CompletableFuture getObject(
.withMetricCollector(apiCallMetricCollector)
// TODO: Deprecate IS_DISCOVERED_ENDPOINT, use new SKIP_ENDPOINT_RESOLUTION for better semantics
.putExecutionAttribute(SdkInternalExecutionAttribute.IS_DISCOVERED_ENDPOINT, true)
- .withMarshaller(new PresignedUrlGetObjectRequestMarshaller(protocolFactory)),
+ .withMarshaller(new PresignedUrlDownloadRequestMarshaller(protocolFactory)),
asyncResponseTransformer);
CompletableFuture whenCompleteFuture = executeFuture.whenComplete((r, e) -> {
diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/presignedurl/PresignedUrlGetObjectRequestMarshaller.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/presignedurl/PresignedUrlDownloadRequestMarshaller.java
similarity index 78%
rename from services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/presignedurl/PresignedUrlGetObjectRequestMarshaller.java
rename to services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/presignedurl/PresignedUrlDownloadRequestMarshaller.java
index 76e1500f2d54..f0aecfce87da 100644
--- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/presignedurl/PresignedUrlGetObjectRequestMarshaller.java
+++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/presignedurl/PresignedUrlDownloadRequestMarshaller.java
@@ -24,11 +24,11 @@
import software.amazon.awssdk.protocols.core.OperationInfo;
import software.amazon.awssdk.protocols.core.ProtocolMarshaller;
import software.amazon.awssdk.protocols.xml.AwsXmlProtocolFactory;
-import software.amazon.awssdk.services.s3.internal.presignedurl.model.PresignedUrlGetObjectRequestWrapper;
+import software.amazon.awssdk.services.s3.internal.presignedurl.model.PresignedUrlDownloadRequestWrapper;
import software.amazon.awssdk.utils.Validate;
/**
- * {@link PresignedUrlGetObjectRequestWrapper} Marshaller
+ * {@link PresignedUrlDownloadRequestWrapper} Marshaller
*
*
* Marshalls presigned URL requests by using the complete URL directly and adding optional Range headers.
@@ -36,7 +36,7 @@
*
*/
@SdkInternalApi
-public class PresignedUrlGetObjectRequestMarshaller implements Marshaller {
+public class PresignedUrlDownloadRequestMarshaller implements Marshaller {
private static final OperationInfo SDK_OPERATION_BINDING = OperationInfo.builder()
.requestUri("").httpMethod(SdkHttpMethod.GET).hasExplicitPayloadMember(false).hasPayloadMembers(false)
.putAdditionalMetadata(AwsXmlProtocolFactory.ROOT_MARSHALL_LOCATION_ATTRIBUTE, null)
@@ -44,26 +44,26 @@ public class PresignedUrlGetObjectRequestMarshaller implements Marshaller protocolMarshaller = protocolFactory
.createProtocolMarshaller(SDK_OPERATION_BINDING);
- URI presignedUri = presignedUrlGetObjectRequestWrapper.url().toURI();
+ URI presignedUri = presignedUrlDownloadRequestWrapper.url().toURI();
- return protocolMarshaller.marshall(presignedUrlGetObjectRequestWrapper)
+ return protocolMarshaller.marshall(presignedUrlDownloadRequestWrapper)
.toBuilder()
.uri(presignedUri)
.build();
diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/presignedurl/model/PresignedUrlGetObjectRequestWrapper.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/presignedurl/model/PresignedUrlDownloadRequestWrapper.java
similarity index 86%
rename from services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/presignedurl/model/PresignedUrlGetObjectRequestWrapper.java
rename to services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/presignedurl/model/PresignedUrlDownloadRequestWrapper.java
index efd5a9f78167..983c6b9c13eb 100644
--- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/presignedurl/model/PresignedUrlGetObjectRequestWrapper.java
+++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/presignedurl/model/PresignedUrlDownloadRequestWrapper.java
@@ -37,14 +37,14 @@
* SdkField definitions needed for custom marshalling and is not intended for direct use by SDK users.
*
* Note: This is an internal implementation class and should not be used
- * directly. Use {@code PresignedUrlGetObjectRequest} for public API interactions.
+ * directly. Use {@code PresignedUrlDownloadRequest} for public API interactions.
*/
@SdkInternalApi
-public final class PresignedUrlGetObjectRequestWrapper extends S3Request {
+public final class PresignedUrlDownloadRequestWrapper extends S3Request {
private static final SdkField RANGE_FIELD = SdkField
.builder(MarshallingType.STRING)
.memberName("Range")
- .getter(getter(PresignedUrlGetObjectRequestWrapper::range))
+ .getter(getter(PresignedUrlDownloadRequestWrapper::range))
.traits(LocationTrait.builder().location(MarshallLocation.HEADER).locationName("Range")
.unmarshallLocationName("Range").build()).build();
@@ -56,7 +56,7 @@ public final class PresignedUrlGetObjectRequestWrapper extends S3Request {
private final URL url;
private final String range;
- private PresignedUrlGetObjectRequestWrapper(Builder builder) {
+ private PresignedUrlDownloadRequestWrapper(Builder builder) {
super(builder);
this.url = builder.url;
this.range = builder.range;
@@ -80,8 +80,8 @@ public Map> sdkFieldNameToField() {
return SDK_NAME_TO_FIELD;
}
- private static Function getter(Function g) {
- return obj -> g.apply((PresignedUrlGetObjectRequestWrapper) obj);
+ private static Function getter(Function g) {
+ return obj -> g.apply((PresignedUrlDownloadRequestWrapper) obj);
}
private static Map> memberNameToFieldInitializer() {
@@ -110,7 +110,7 @@ public boolean equals(Object obj) {
if (!super.equals(obj)) {
return false;
}
- PresignedUrlGetObjectRequestWrapper that = (PresignedUrlGetObjectRequestWrapper) obj;
+ PresignedUrlDownloadRequestWrapper that = (PresignedUrlDownloadRequestWrapper) obj;
return Objects.equals(url, that.url) && Objects.equals(range, that.range);
}
@@ -129,7 +129,7 @@ public static final class Builder extends S3Request.BuilderImpl {
public Builder() {
}
- Builder(PresignedUrlGetObjectRequestWrapper request) {
+ Builder(PresignedUrlDownloadRequestWrapper request) {
super(request);
this.url = request.url();
this.range = request.range();
@@ -146,8 +146,8 @@ public Builder range(String range) {
}
@Override
- public PresignedUrlGetObjectRequestWrapper build() {
- return new PresignedUrlGetObjectRequestWrapper(this);
+ public PresignedUrlDownloadRequestWrapper build() {
+ return new PresignedUrlDownloadRequestWrapper(this);
}
}
}
diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/presignedurl/AsyncPresignedUrlManager.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/presignedurl/AsyncPresignedUrlExtension.java
similarity index 90%
rename from services/s3/src/main/java/software/amazon/awssdk/services/s3/presignedurl/AsyncPresignedUrlManager.java
rename to services/s3/src/main/java/software/amazon/awssdk/services/s3/presignedurl/AsyncPresignedUrlExtension.java
index 93333a684ff5..545eb838c35e 100644
--- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/presignedurl/AsyncPresignedUrlManager.java
+++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/presignedurl/AsyncPresignedUrlExtension.java
@@ -24,14 +24,14 @@
import software.amazon.awssdk.services.s3.S3AsyncClient;
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
import software.amazon.awssdk.services.s3.model.S3Exception;
-import software.amazon.awssdk.services.s3.presignedurl.model.PresignedUrlGetObjectRequest;
+import software.amazon.awssdk.services.s3.presignedurl.model.PresignedUrlDownloadRequest;
/**
* Interface for executing S3 operations asynchronously using presigned URLs. This can be accessed using
- * {@link S3AsyncClient#presignedUrlManager()}.
+ * {@link S3AsyncClient#presignedUrlExtension()}.
*/
@SdkPublicApi
-public interface AsyncPresignedUrlManager {
+public interface AsyncPresignedUrlExtension {
/**
*
* Downloads an S3 object asynchronously using a presigned URL.
@@ -52,7 +52,7 @@ public interface AsyncPresignedUrlManager {
* @throws SdkClientException If any client side error occurs
* @throws S3Exception Base class for all S3 service exceptions
*/
- default CompletableFuture getObject(PresignedUrlGetObjectRequest request,
+ default CompletableFuture getObject(PresignedUrlDownloadRequest request,
AsyncResponseTransformer responseTransformer) {
throw new UnsupportedOperationException();
@@ -64,20 +64,20 @@ default CompletableFuture getObject(PresignedUrlGetObjectRequ
*
*
*
- * This is a convenience method that creates a {@link PresignedUrlGetObjectRequest} using the provided consumer.
+ * This is a convenience method that creates a {@link PresignedUrlDownloadRequest} using the provided consumer.
*
*
- * @param requestConsumer Consumer that will configure a {@link PresignedUrlGetObjectRequest.Builder}
+ * @param requestConsumer Consumer that will configure a {@link PresignedUrlDownloadRequest.Builder}
* @param responseTransformer Transforms the response to the desired return type
* @param The type of the transformed response
* @return A {@link CompletableFuture} containing the transformed result
* @throws SdkClientException If any client side error occurs
* @throws S3Exception Base class for all S3 service exceptions
*/
- default CompletableFuture getObject(Consumer requestConsumer,
+ default CompletableFuture getObject(Consumer requestConsumer,
AsyncResponseTransformer responseTransformer) {
- return getObject(PresignedUrlGetObjectRequest.builder().applyMutation(requestConsumer).build(), responseTransformer);
+ return getObject(PresignedUrlDownloadRequest.builder().applyMutation(requestConsumer).build(), responseTransformer);
}
/**
@@ -96,7 +96,7 @@ default CompletableFuture getObject(Consumer getObject(PresignedUrlGetObjectRequest request,
+ default CompletableFuture getObject(PresignedUrlDownloadRequest request,
Path destinationPath) {
return getObject(request, AsyncResponseTransformer.toFile(destinationPath));
}
@@ -111,13 +111,13 @@ default CompletableFuture getObject(PresignedUrlGetObjectRequ
* This is a convenience method that combines consumer-based request building with file-based response handling.
*
*
- * @param requestConsumer Consumer that will configure a {@link PresignedUrlGetObjectRequest.Builder}
+ * @param requestConsumer Consumer that will configure a {@link PresignedUrlDownloadRequest.Builder}
* @param destinationPath The path where the downloaded object will be saved
* @return A {@link CompletableFuture} containing the {@link GetObjectResponse}
* @throws SdkClientException If any client side error occurs
* @throws S3Exception Base class for all S3 service exceptions
*/
- default CompletableFuture getObject(Consumer requestConsumer,
+ default CompletableFuture getObject(Consumer requestConsumer,
Path destinationPath) {
return getObject(requestConsumer, AsyncResponseTransformer.toFile(destinationPath));
}
diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/presignedurl/model/PresignedUrlGetObjectRequest.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/presignedurl/model/PresignedUrlDownloadRequest.java
similarity index 77%
rename from services/s3/src/main/java/software/amazon/awssdk/services/s3/presignedurl/model/PresignedUrlGetObjectRequest.java
rename to services/s3/src/main/java/software/amazon/awssdk/services/s3/presignedurl/model/PresignedUrlDownloadRequest.java
index 20cd9c89d8a2..380c234b82c8 100644
--- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/presignedurl/model/PresignedUrlGetObjectRequest.java
+++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/presignedurl/model/PresignedUrlDownloadRequest.java
@@ -17,7 +17,6 @@
import java.net.URL;
import java.util.Objects;
-import java.util.function.Consumer;
import software.amazon.awssdk.annotations.SdkPublicApi;
import software.amazon.awssdk.utils.ToString;
import software.amazon.awssdk.utils.Validate;
@@ -25,15 +24,15 @@
import software.amazon.awssdk.utils.builder.ToCopyableBuilder;
/**
- * Request object for performing GetObject operations using a presigned URL.
+ * Request object for performing download operations using a presigned URL.
*/
@SdkPublicApi
-public final class PresignedUrlGetObjectRequest implements ToCopyableBuilder {
+public final class PresignedUrlDownloadRequest implements ToCopyableBuilder {
private final URL presignedUrl;
private final String range;
- private PresignedUrlGetObjectRequest(BuilderImpl builder) {
+ private PresignedUrlDownloadRequest(BuilderImpl builder) {
this.presignedUrl = builder.presignedUrl;
this.range = builder.range;
}
@@ -75,16 +74,6 @@ public static Builder builder() {
return new BuilderImpl();
}
- /**
- * Create a {@code PresignedUrlGetObjectRequest} instance with the given configuration
- *
- * @param builderConsumer the consumer that will configure the builder
- * @return a {@code PresignedUrlGetObjectRequest} instance
- */
- public static PresignedUrlGetObjectRequest builder(Consumer builderConsumer) {
- return builder().applyMutation(builderConsumer).build();
- }
-
public static Class extends Builder> serializableBuilderClass() {
return BuilderImpl.class;
}
@@ -105,20 +94,20 @@ public boolean equals(Object obj) {
if (obj == null || getClass() != obj.getClass()) {
return false;
}
- PresignedUrlGetObjectRequest other = (PresignedUrlGetObjectRequest) obj;
+ PresignedUrlDownloadRequest other = (PresignedUrlDownloadRequest) obj;
return Objects.equals(presignedUrl(), other.presignedUrl()) &&
Objects.equals(range(), other.range());
}
@Override
public String toString() {
- return ToString.builder("PresignedUrlGetObjectRequest")
+ return ToString.builder("PresignedUrlDownloadRequest")
.add("PresignedUrl", presignedUrl())
.add("Range", range())
.build();
}
- public interface Builder extends CopyableBuilder {
+ public interface Builder extends CopyableBuilder {
/**
* Sets the presigned URL for the S3 object.
* @param presignedUrl
@@ -141,9 +130,9 @@ static final class BuilderImpl implements Builder {
private BuilderImpl() {
}
- private BuilderImpl(PresignedUrlGetObjectRequest presignedUrlGetObjectRequest) {
- presignedUrl(presignedUrlGetObjectRequest.presignedUrl());
- range(presignedUrlGetObjectRequest.range());
+ private BuilderImpl(PresignedUrlDownloadRequest presignedUrlDownloadRequest) {
+ presignedUrl(presignedUrlDownloadRequest.presignedUrl());
+ range(presignedUrlDownloadRequest.range());
}
@Override
@@ -159,9 +148,9 @@ public Builder range(String range) {
}
@Override
- public PresignedUrlGetObjectRequest build() {
+ public PresignedUrlDownloadRequest build() {
Validate.paramNotNull(presignedUrl, "presignedUrl");
- return new PresignedUrlGetObjectRequest(this);
+ return new PresignedUrlDownloadRequest(this);
}
}
}
diff --git a/services/s3/src/main/resources/codegen-resources/customization.config b/services/s3/src/main/resources/codegen-resources/customization.config
index 0d9b8038bd1f..e266868be2e3 100644
--- a/services/s3/src/main/resources/codegen-resources/customization.config
+++ b/services/s3/src/main/resources/codegen-resources/customization.config
@@ -359,5 +359,5 @@
"className": "software.amazon.awssdk.services.s3.internal.CustomRequestTransformerUtils"
}
},
- "presignedUrlManagerSupported": true
+ "presignedUrlExtensionSupported": true
}
diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/DefaultAsyncPresignedUrlManagerTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/DefaultAsyncPresignedUrlExtensionTest.java
similarity index 88%
rename from services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/DefaultAsyncPresignedUrlManagerTest.java
rename to services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/DefaultAsyncPresignedUrlExtensionTest.java
index e569287aa582..91681fbb8e15 100644
--- a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/DefaultAsyncPresignedUrlManagerTest.java
+++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/DefaultAsyncPresignedUrlExtensionTest.java
@@ -67,13 +67,13 @@
import software.amazon.awssdk.services.s3.model.InvalidObjectStateException;
import software.amazon.awssdk.services.s3.model.NoSuchKeyException;
import software.amazon.awssdk.services.s3.model.S3Exception;
-import software.amazon.awssdk.services.s3.presignedurl.model.PresignedUrlGetObjectRequest;
+import software.amazon.awssdk.services.s3.presignedurl.model.PresignedUrlDownloadRequest;
import software.amazon.awssdk.testutils.service.http.MockAsyncHttpClient;
/**
- * Tests for {@link DefaultAsyncPresignedUrlManager} using MockAsyncHttpClient to verify HTTP interactions.
+ * Tests for {@link DefaultAsyncPresignedUrlExtension} using MockAsyncHttpClient to verify HTTP interactions.
*/
-class DefaultAsyncPresignedUrlManagerTest {
+class DefaultAsyncPresignedUrlExtensionTest {
private static final String TEST_CONTENT = "test-content";
private static final URI DEFAULT_ENDPOINT = URI.create("https://defaultendpoint.com");
@@ -87,8 +87,8 @@ class DefaultAsyncPresignedUrlManagerTest {
"X-Amz-Expires=86400";
private MockAsyncHttpClient mockHttpClient;
- private DefaultAsyncPresignedUrlManager presignedUrlManager;
- private PresignedUrlGetObjectRequest testRequest;
+ private DefaultAsyncPresignedUrlExtension presignedUrlExtension;
+ private PresignedUrlDownloadRequest testRequest;
private AwsProtocolMetadata protocolMetadata;
private AwsS3ProtocolFactory protocolFactory;
private AsyncClientHandler clientHandler;
@@ -97,7 +97,7 @@ class DefaultAsyncPresignedUrlManagerTest {
void setUp() throws Exception {
mockHttpClient = new MockAsyncHttpClient();
URL testPresignedUrl = new URL(TEST_URL);
- testRequest = PresignedUrlGetObjectRequest.builder()
+ testRequest = PresignedUrlDownloadRequest.builder()
.presignedUrl(testPresignedUrl)
.build();
@@ -108,7 +108,7 @@ void setUp() throws Exception {
protocolFactory = initProtocolFactory(clientConfiguration);
clientHandler = new AwsAsyncClientHandler(clientConfiguration);
- presignedUrlManager = new DefaultAsyncPresignedUrlManager(
+ presignedUrlExtension = new DefaultAsyncPresignedUrlExtension(
clientHandler, protocolFactory, clientConfiguration, protocolMetadata);
}
@@ -145,13 +145,13 @@ private static Stream requestConfigurationTestCases() {
return Stream.of(
Arguments.of(
"Basic request",
- (Consumer) builder ->
+ (Consumer) builder ->
builder.presignedUrl(createTestUrl()),
null
),
Arguments.of(
"Request with range header",
- (Consumer) builder ->
+ (Consumer) builder ->
builder.presignedUrl(createTestUrl()).range("bytes=0-1024"),
"bytes=0-1024"
)
@@ -176,7 +176,7 @@ private static Stream invalidUrlTestCases() {
@ParameterizedTest(name = "{0}")
@MethodSource("httpResponseTestCases")
- void given_AsyncPresignedUrlManager_when_GetObjectWithDifferentHttpResponses_then_ShouldHandleSuccessAndErrorsCorrectly(
+ void given_AsyncPresignedUrlExtension_when_GetObjectWithDifferentHttpResponses_then_ShouldHandleSuccessAndErrorsCorrectly(
String testName,
HttpExecuteResponse response,
boolean expectSuccess,
@@ -188,7 +188,7 @@ void given_AsyncPresignedUrlManager_when_GetObjectWithDifferentHttpResponses_the
assertSuccessfulGetObject(testRequest);
} else {
CompletableFuture> future =
- presignedUrlManager.getObject(testRequest, AsyncResponseTransformer.toBytes());
+ presignedUrlExtension.getObject(testRequest, AsyncResponseTransformer.toBytes());
assertThatThrownBy(future::join)
.hasCauseInstanceOf(expectedExceptionType);
@@ -197,19 +197,19 @@ void given_AsyncPresignedUrlManager_when_GetObjectWithDifferentHttpResponses_the
@ParameterizedTest(name = "{0}")
@MethodSource("requestConfigurationTestCases")
- void given_AsyncPresignedUrlManager_when_GetObjectWithDifferentRequestConfigurations_then_ShouldSetCorrectHeaders(
+ void given_AsyncPresignedUrlExtension_when_GetObjectWithDifferentRequestConfigurations_then_ShouldSetCorrectHeaders(
String testName,
- Consumer requestCustomizer,
+ Consumer requestCustomizer,
String expectedRangeHeader) throws ExecutionException, InterruptedException {
mockHttpClient.stubNextResponse(createSuccessResponse());
- PresignedUrlGetObjectRequest.Builder builder = PresignedUrlGetObjectRequest.builder();
+ PresignedUrlDownloadRequest.Builder builder = PresignedUrlDownloadRequest.builder();
requestCustomizer.accept(builder);
- PresignedUrlGetObjectRequest request = builder.build();
+ PresignedUrlDownloadRequest request = builder.build();
CompletableFuture> future =
- presignedUrlManager.getObject(request, AsyncResponseTransformer.toBytes());
+ presignedUrlExtension.getObject(request, AsyncResponseTransformer.toBytes());
ResponseBytes result = future.get();
assertThat(result).isNotNull();
@@ -227,14 +227,14 @@ void given_AsyncPresignedUrlManager_when_GetObjectWithDifferentRequestConfigurat
@ParameterizedTest(name = "{0}")
@MethodSource("additionalTestCases")
- void given_AsyncPresignedUrlManager_when_ExecutingDifferentScenarios_then_ShouldBehaveCorrectly(
+ void given_AsyncPresignedUrlExtension_when_ExecutingDifferentScenarios_then_ShouldBehaveCorrectly(
String testName, String testType) throws Exception {
switch (testType) {
case "CUSTOM_TRANSFORMER":
mockHttpClient.stubNextResponse(createSuccessResponse());
CompletableFuture> future =
- presignedUrlManager.getObject(testRequest, AsyncResponseTransformer.toBytes());
+ presignedUrlExtension.getObject(testRequest, AsyncResponseTransformer.toBytes());
ResponseBytes result = future.get();
assertThat(result.asUtf8String()).isEqualTo(TEST_CONTENT);
break;
@@ -244,11 +244,11 @@ void given_AsyncPresignedUrlManager_when_ExecutingDifferentScenarios_then_Should
SdkClientConfiguration clientConfigWithMetrics = getDefaultSdkConfigs().toBuilder()
.option(SdkClientOption.METRIC_PUBLISHERS, Collections.singletonList(mockPublisher))
.build();
- DefaultAsyncPresignedUrlManager managerWithMetrics = new DefaultAsyncPresignedUrlManager(
+ DefaultAsyncPresignedUrlExtension extensionWithMetrics = new DefaultAsyncPresignedUrlExtension(
clientHandler, protocolFactory, clientConfigWithMetrics, protocolMetadata);
mockHttpClient.stubNextResponse(createSuccessResponse());
CompletableFuture> metricsFuture =
- managerWithMetrics.getObject(testRequest, AsyncResponseTransformer.toBytes());
+ extensionWithMetrics.getObject(testRequest, AsyncResponseTransformer.toBytes());
metricsFuture.get();
verify(mockPublisher, atLeastOnce()).publish(any(MetricCollection.class));
@@ -256,21 +256,21 @@ void given_AsyncPresignedUrlManager_when_ExecutingDifferentScenarios_then_Should
verify(mockPublisher).publish(metricsCaptor.capture());
MetricCollection capturedMetrics = metricsCaptor.getValue();
assertThat(capturedMetrics.metricValues(CoreMetric.SERVICE_ID)).contains("S3");
- assertThat(capturedMetrics.metricValues(CoreMetric.OPERATION_NAME)).contains("PresignedUrlGetObject");
+ assertThat(capturedMetrics.metricValues(CoreMetric.OPERATION_NAME)).contains("PresignedUrlDownload");
break;
}
}
@ParameterizedTest(name = "{0}")
@MethodSource("invalidUrlTestCases")
- void given_AsyncPresignedUrlManager_when_GetObjectWithInvalidUrl_then_ShouldThrowException(
+ void given_AsyncPresignedUrlExtension_when_GetObjectWithInvalidUrl_then_ShouldThrowException(
String testName, String invalidUrlString) {
assertThatThrownBy(() -> {
URL invalidUrl = new URL(invalidUrlString);
- PresignedUrlGetObjectRequest invalidRequest = PresignedUrlGetObjectRequest.builder()
+ PresignedUrlDownloadRequest invalidRequest = PresignedUrlDownloadRequest.builder()
.presignedUrl(invalidUrl)
.build();
- presignedUrlManager.getObject(invalidRequest, AsyncResponseTransformer.toBytes()).join();
+ presignedUrlExtension.getObject(invalidRequest, AsyncResponseTransformer.toBytes()).join();
}).satisfiesAnyOf(
ex -> assertThat(ex).isInstanceOf(java.net.MalformedURLException.class),
ex -> assertThat(ex).isInstanceOf(SdkClientException.class),
@@ -324,10 +324,10 @@ private static URL createTestUrl() {
}
}
- private void assertSuccessfulGetObject(PresignedUrlGetObjectRequest request) {
+ private void assertSuccessfulGetObject(PresignedUrlDownloadRequest request) {
try {
CompletableFuture> future =
- presignedUrlManager.getObject(request, AsyncResponseTransformer.toBytes());
+ presignedUrlExtension.getObject(request, AsyncResponseTransformer.toBytes());
ResponseBytes result = future.get();
assertThat(result).isNotNull();
diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/PresignedUrlGetObjectRequestMarshallerTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/PresignedUrlDownloadRequestMarshallerTest.java
similarity index 88%
rename from services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/PresignedUrlGetObjectRequestMarshallerTest.java
rename to services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/PresignedUrlDownloadRequestMarshallerTest.java
index 2311017a29e5..b89794a2a59c 100644
--- a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/PresignedUrlGetObjectRequestMarshallerTest.java
+++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/PresignedUrlDownloadRequestMarshallerTest.java
@@ -32,11 +32,11 @@
import software.amazon.awssdk.protocols.core.OperationInfo;
import software.amazon.awssdk.protocols.core.ProtocolMarshaller;
import software.amazon.awssdk.protocols.xml.AwsXmlProtocolFactory;
-import software.amazon.awssdk.services.s3.internal.presignedurl.model.PresignedUrlGetObjectRequestWrapper;
+import software.amazon.awssdk.services.s3.internal.presignedurl.model.PresignedUrlDownloadRequestWrapper;
-class PresignedUrlGetObjectRequestMarshallerTest {
+class PresignedUrlDownloadRequestMarshallerTest {
- private PresignedUrlGetObjectRequestMarshaller marshaller;
+ private PresignedUrlDownloadRequestMarshaller marshaller;
private AwsXmlProtocolFactory mockProtocolFactory;
private ProtocolMarshaller mockProtocolMarshaller;
private URL testUrl;
@@ -47,7 +47,7 @@ void setUp() throws Exception {
mockProtocolMarshaller = mock(ProtocolMarshaller.class);
when(mockProtocolFactory.createProtocolMarshaller(any(OperationInfo.class)))
.thenReturn(mockProtocolMarshaller);
- marshaller = new PresignedUrlGetObjectRequestMarshaller(mockProtocolFactory);
+ marshaller = new PresignedUrlDownloadRequestMarshaller(mockProtocolFactory);
testUrl = new URL("https://test-bucket.s3.us-east-1.amazonaws.com/test-key?" +
"X-Amz-Date=20231215T000000Z&" +
@@ -67,10 +67,10 @@ void marshall_withBasicRequest_shouldCreateCorrectHttpRequest() throws Exception
.protocol("https")
.host("example.com")
.build();
- when(mockProtocolMarshaller.marshall(any(PresignedUrlGetObjectRequestWrapper.class)))
+ when(mockProtocolMarshaller.marshall(any(PresignedUrlDownloadRequestWrapper.class)))
.thenReturn(baseRequest);
- PresignedUrlGetObjectRequestWrapper request = PresignedUrlGetObjectRequestWrapper.builder()
+ PresignedUrlDownloadRequestWrapper request = PresignedUrlDownloadRequestWrapper.builder()
.url(testUrl)
.build();
SdkHttpFullRequest result = marshaller.marshall(request);
@@ -114,10 +114,10 @@ void marshall_withValidRangeFormats_shouldAddRangeHeader(String rangeValue) thro
.putHeader("Range", rangeValue) // Add the Range header to the mock response
.build();
- when(mockProtocolMarshaller.marshall(any(PresignedUrlGetObjectRequestWrapper.class)))
+ when(mockProtocolMarshaller.marshall(any(PresignedUrlDownloadRequestWrapper.class)))
.thenReturn(baseRequest);
- PresignedUrlGetObjectRequestWrapper request = PresignedUrlGetObjectRequestWrapper.builder()
+ PresignedUrlDownloadRequestWrapper request = PresignedUrlDownloadRequestWrapper.builder()
.url(testUrl)
.range(rangeValue)
.build();
@@ -139,10 +139,10 @@ void marshall_withNullOrEmptyRange_shouldNotAddRangeHeader(String rangeValue) th
.protocol("https")
.host("example.com")
.build();
- when(mockProtocolMarshaller.marshall(any(PresignedUrlGetObjectRequestWrapper.class)))
+ when(mockProtocolMarshaller.marshall(any(PresignedUrlDownloadRequestWrapper.class)))
.thenReturn(baseRequest);
- PresignedUrlGetObjectRequestWrapper request = PresignedUrlGetObjectRequestWrapper.builder()
+ PresignedUrlDownloadRequestWrapper request = PresignedUrlDownloadRequestWrapper.builder()
.url(testUrl)
.range(rangeValue)
.build();
@@ -155,7 +155,7 @@ void marshall_withNullOrEmptyRange_shouldNotAddRangeHeader(String rangeValue) th
void marshall_withNullRequest_shouldThrowException() {
assertThatThrownBy(() -> marshaller.marshall(null))
.isInstanceOf(NullPointerException.class)
- .hasMessageContaining("presignedUrlGetObjectRequestWrapper must not be null");
+ .hasMessageContaining("presignedUrlDownloadRequestWrapper must not be null");
}
@Test
@@ -166,11 +166,11 @@ void marshall_withMalformedUrl_shouldThrowSdkClientException() throws Exception
.protocol("https")
.host("example.com")
.build();
- when(mockProtocolMarshaller.marshall(any(PresignedUrlGetObjectRequestWrapper.class)))
+ when(mockProtocolMarshaller.marshall(any(PresignedUrlDownloadRequestWrapper.class)))
.thenReturn(baseRequest);
URL malformedUrl = new URL("https", "test-bucket.s3.us-east-1.amazonaws.com", -1, "/test key with spaces");
- PresignedUrlGetObjectRequestWrapper request = PresignedUrlGetObjectRequestWrapper.builder()
+ PresignedUrlDownloadRequestWrapper request = PresignedUrlDownloadRequestWrapper.builder()
.url(malformedUrl)
.build();
diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/model/PresignedUrlGetObjectRequestWrapperTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/model/PresignedUrlDownloadRequestWrapperTest.java
similarity index 87%
rename from services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/model/PresignedUrlGetObjectRequestWrapperTest.java
rename to services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/model/PresignedUrlDownloadRequestWrapperTest.java
index 8334050fa7c8..c1280667eb26 100644
--- a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/model/PresignedUrlGetObjectRequestWrapperTest.java
+++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/model/PresignedUrlDownloadRequestWrapperTest.java
@@ -26,17 +26,17 @@
import software.amazon.awssdk.core.protocol.MarshallLocation;
-class PresignedUrlGetObjectRequestWrapperTest {
+class PresignedUrlDownloadRequestWrapperTest {
@Test
void equalsAndHashCode_shouldFollowContract() {
- EqualsVerifier.forClass(PresignedUrlGetObjectRequestWrapper.class)
+ EqualsVerifier.forClass(PresignedUrlDownloadRequestWrapper.class)
.withRedefinedSuperclass()
.verify();
}
@Test
void basicProperties_shouldWork() throws Exception {
- PresignedUrlGetObjectRequestWrapper request = PresignedUrlGetObjectRequestWrapper.builder()
+ PresignedUrlDownloadRequestWrapper request = PresignedUrlDownloadRequestWrapper.builder()
.url(new URL("https://example.com"))
.range("bytes=0-100")
.build();
@@ -47,7 +47,7 @@ void basicProperties_shouldWork() throws Exception {
@Test
void sdkFields_shouldReturnExpectedFields() throws Exception {
- PresignedUrlGetObjectRequestWrapper request = PresignedUrlGetObjectRequestWrapper.builder()
+ PresignedUrlDownloadRequestWrapper request = PresignedUrlDownloadRequestWrapper.builder()
.url(new URL("https://example.com"))
.range("bytes=0-100")
.build();
@@ -66,7 +66,7 @@ void sdkFields_shouldReturnExpectedFields() throws Exception {
@Test
void sdkFieldNameToField_shouldReturnExpectedMapping() throws Exception {
- PresignedUrlGetObjectRequestWrapper request = PresignedUrlGetObjectRequestWrapper.builder()
+ PresignedUrlDownloadRequestWrapper request = PresignedUrlDownloadRequestWrapper.builder()
.url(new URL("https://example.com"))
.build();
@@ -80,7 +80,7 @@ void sdkFieldNameToField_shouldReturnExpectedMapping() throws Exception {
@Test
void rangeField_shouldMarshalCorrectly() throws Exception {
- PresignedUrlGetObjectRequestWrapper request = PresignedUrlGetObjectRequestWrapper.builder()
+ PresignedUrlDownloadRequestWrapper request = PresignedUrlDownloadRequestWrapper.builder()
.url(new URL("https://example.com"))
.range("bytes=0-1023")
.build();
diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/presignedurl/AsyncPresignedUrlManagerTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/presignedurl/AsyncPresignedUrlExtensionTest.java
similarity index 89%
rename from services/s3/src/test/java/software/amazon/awssdk/services/s3/presignedurl/AsyncPresignedUrlManagerTest.java
rename to services/s3/src/test/java/software/amazon/awssdk/services/s3/presignedurl/AsyncPresignedUrlExtensionTest.java
index 3441b46d142c..1b99c9bde491 100644
--- a/services/s3/src/test/java/software/amazon/awssdk/services/s3/presignedurl/AsyncPresignedUrlManagerTest.java
+++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/presignedurl/AsyncPresignedUrlExtensionTest.java
@@ -52,13 +52,13 @@
import software.amazon.awssdk.core.exception.SdkClientException;
import software.amazon.awssdk.services.s3.S3AsyncClient;
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
-import software.amazon.awssdk.services.s3.presignedurl.model.PresignedUrlGetObjectRequest;
+import software.amazon.awssdk.services.s3.presignedurl.model.PresignedUrlDownloadRequest;
@WireMockTest
-public class AsyncPresignedUrlManagerTest {
+public class AsyncPresignedUrlExtensionTest {
private S3AsyncClient s3AsyncClient;
- private AsyncPresignedUrlManager presignedUrlManager;
+ private AsyncPresignedUrlExtension presignedUrlExtension;
@TempDir
Path tempDir;
@@ -68,7 +68,7 @@ void setup(WireMockRuntimeInfo wireMockRuntimeInfo) {
s3AsyncClient = S3AsyncClient.builder()
.endpointOverride(URI.create(wireMockRuntimeInfo.getHttpBaseUrl()))
.build();
- presignedUrlManager = s3AsyncClient.presignedUrlManager();
+ presignedUrlExtension = s3AsyncClient.presignedUrlExtension();
}
@AfterEach
@@ -81,8 +81,8 @@ void cleanup() {
@Nested
class BasicFunctionality {
@Test
- void givenS3AsyncClient_whenPresignedUrlManagerRequested_thenReturnsNonNullInstance() {
- assertThat(presignedUrlManager).isNotNull();
+ void givenS3AsyncClient_whenPresignedUrlExtensionRequested_thenReturnsNonNullInstance() {
+ assertThat(presignedUrlExtension).isNotNull();
}
@Test
@@ -94,10 +94,10 @@ void givenValidPresignedUrl_whenGetObjectCalled_thenReturnsExpectedContent(WireM
stubSuccessResponse(presignedUrlPath, testContent, testETag);
URL presignedUrl = createPresignedUrl(wireMockRuntimeInfo, presignedUrlPath);
- PresignedUrlGetObjectRequest request = createRequest(presignedUrl);
+ PresignedUrlDownloadRequest request = createRequest(presignedUrl);
CompletableFuture> result =
- presignedUrlManager.getObject(request, AsyncResponseTransformer.toBytes());
+ presignedUrlExtension.getObject(request, AsyncResponseTransformer.toBytes());
ResponseBytes response = result.get();
assertSuccessfulResponse(response, testContent, testETag);
@@ -114,7 +114,7 @@ void givenValidPresignedUrl_whenGetObjectWithConsumerBuilderCalled_thenReturnsEx
URL presignedUrl = createPresignedUrl(wireMockRuntimeInfo, presignedUrlPath);
CompletableFuture> result =
- presignedUrlManager.getObject(
+ presignedUrlExtension.getObject(
request -> request.presignedUrl(presignedUrl),
AsyncResponseTransformer.toBytes()
);
@@ -138,10 +138,10 @@ void givenEmptyFile_whenGetObjectCalled_thenReturnsEmptyContent(WireMockRuntimeI
.withBody(testContent)));
URL presignedUrl = createPresignedUrl(wireMockRuntimeInfo, presignedUrlPath);
- PresignedUrlGetObjectRequest request = createRequest(presignedUrl);
+ PresignedUrlDownloadRequest request = createRequest(presignedUrl);
CompletableFuture> result =
- presignedUrlManager.getObject(request, AsyncResponseTransformer.toBytes());
+ presignedUrlExtension.getObject(request, AsyncResponseTransformer.toBytes());
ResponseBytes response = result.get();
assertThat(response).isNotNull();
@@ -166,13 +166,13 @@ void givenRangeRequest_whenGetObjectCalled_thenReturnsPartialContent(WireMockRun
.withBody(expectedContent)));
URL presignedUrl = createPresignedUrl(wireMockRuntimeInfo, presignedUrlPath);
- PresignedUrlGetObjectRequest request = PresignedUrlGetObjectRequest.builder()
+ PresignedUrlDownloadRequest request = PresignedUrlDownloadRequest.builder()
.presignedUrl(presignedUrl)
.range(range)
.build();
CompletableFuture> result =
- presignedUrlManager.getObject(request, AsyncResponseTransformer.toBytes());
+ presignedUrlExtension.getObject(request, AsyncResponseTransformer.toBytes());
ResponseBytes response = result.get();
assertThat(response).isNotNull();
@@ -191,12 +191,12 @@ void givenValidPresignedUrl_whenGetObjectToFileCalled_thenDownloadsFileWithExpec
stubSuccessResponse(presignedUrlPath, testContent, testETag);
URL presignedUrl = createPresignedUrl(wireMockRuntimeInfo, presignedUrlPath);
- PresignedUrlGetObjectRequest request = createRequest(presignedUrl);
+ PresignedUrlDownloadRequest request = createRequest(presignedUrl);
Path downloadFile = tempDir.resolve("download-" + UUID.randomUUID() + ".txt");
CompletableFuture result =
- presignedUrlManager.getObject(request, downloadFile);
+ presignedUrlExtension.getObject(request, downloadFile);
GetObjectResponse response = result.get();
assertThat(response).isNotNull();
@@ -218,7 +218,7 @@ void givenValidPresignedUrl_whenGetObjectToFileWithConsumerBuilderCalled_thenDow
Path downloadFile = tempDir.resolve("consumer-download-" + UUID.randomUUID() + ".txt");
CompletableFuture result =
- presignedUrlManager.getObject(
+ presignedUrlExtension.getObject(
request -> request.presignedUrl(presignedUrl),
downloadFile
);
@@ -246,10 +246,10 @@ void givenEmptyFile_whenGetObjectToFileCalled_thenDownloadsEmptyFile(WireMockRun
.withBody(testContent)));
URL presignedUrl = createPresignedUrl(wireMockRuntimeInfo, presignedUrlPath);
- PresignedUrlGetObjectRequest request = createRequest(presignedUrl);
+ PresignedUrlDownloadRequest request = createRequest(presignedUrl);
Path downloadFile = tempDir.resolve("empty-file-" + UUID.randomUUID() + ".txt");
- CompletableFuture result = presignedUrlManager.getObject(request, downloadFile);
+ CompletableFuture result = presignedUrlExtension.getObject(request, downloadFile);
GetObjectResponse response = result.get();
assertThat(response).isNotNull();
@@ -275,10 +275,10 @@ void givenLargeFile_whenGetObjectCalled_thenDownloadsCompleteContent(WireMockRun
.withBody(largeContent)));
URL presignedUrl = createPresignedUrl(wireMockRuntimeInfo, presignedUrlPath);
- PresignedUrlGetObjectRequest request = createRequest(presignedUrl);
+ PresignedUrlDownloadRequest request = createRequest(presignedUrl);
Path downloadFile = tempDir.resolve("large-file-" + UUID.randomUUID() + ".bin");
- CompletableFuture result = presignedUrlManager.getObject(request, downloadFile);
+ CompletableFuture result = presignedUrlExtension.getObject(request, downloadFile);
GetObjectResponse response = result.get();
assertThat(response).isNotNull();
@@ -316,10 +316,10 @@ void givenNetworkError_whenGetObjectCalled_thenRetriesAndEventuallySucceeds(Wire
.withBody(testContent)));
URL presignedUrl = createPresignedUrl(wireMockRuntimeInfo, presignedUrlPath);
- PresignedUrlGetObjectRequest request = createRequest(presignedUrl);
+ PresignedUrlDownloadRequest request = createRequest(presignedUrl);
CompletableFuture